eudora-mac/imapdownload.c

1 line
330 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. */
/* Copyright (c) 1998 by QUALCOMM Incorporated */
#define FILE_NUM 114
#ifdef IMAP
/**********************************************************************
* imapDownload.c
*
* This file contains the functions that download messages and
* message bits from the server.
**********************************************************************/
#include "imapdownload.h"
/* structs and stuff ... */
#define IMAP_SEEN_STRING_FLAG "\\Seen"
#define IMAP_DELETED_STRING_FLAG "\\Deleted"
#define IMAP_FLAGGED_STRING_FLAG "\\Flagged"
#define IMAP_ANSWERED_STRING_FLAG "\\Answered"
#define IMAP_DRAFT_STRING_FLAG "\\Draft"
unsigned char *dashes = "\p--";
// for message decoding
LineIOP Lip;
long gIMAPMsgEnd;
// download error codes
enum
{
IMAPParamErr=1,
IMAPOperationAlreadyUnderwayErr,
IMAPResyncFailed
};
// global handle to list of pending deliveries
DeliveryNodeHandle gDeliveryQueue = nil;
// global handle of uids that need updating on screen
UpdateNodeHandle gWindowUpdates = nil;
// global IMAP Stream for FILTERING only
IMAPStreamPtr gFilterStream = nil;
// global pershandle for FILTERING only
PersHandle gFilterPers = nil;
// global flag to turn off progress messages
Boolean gFilteringUnderway = false;
long gManualFilteringUnderway = false;
// global list of changes needed to be made to POP mailboxes
IMAPAppendHandle gSpoolData = nil;
Boolean gSpoolDataLocked = false;
// globals indicating what mailbox is being filtered
TOCHandle gFilterTocH;
short gNumUnfiltered;
Boolean gFilteringCancelled;
// global indicating incremental searches need to be updated
Boolean gUpdateIncrementalSearches;
// This driver reads messages chunk by chunk from an IMAP server
static void StreamAppendDriverInit(STRING *s, void *data, unsigned long size);
static char StreamAppendDriverNext(STRING *s);
static void StreamAppendDriverSetpos(STRING *s, unsigned long i);
STRINGDRIVER StreamAppendDriver =
{
StreamAppendDriverInit,
StreamAppendDriverNext,
StreamAppendDriverSetpos
};
// STRINGDataStruct. Contains the data necessary for a STRING to connect and fetch a message from the source server
typedef struct STRINGDataStruct STRINGDataStruct, *STRINGDataStructPtr;
struct STRINGDataStruct
{
unsigned long uid; // uid of message to fetch
UPtr buffer; // buffer to store partial fetch in
long bufferSize; // size of buffer
long bytesRead; // number of bytes read
IMAPStreamPtr imapStream; // where to connect to.
Boolean result; // set to FALSE if something's gone wrong
};
// This driver reads messages chunk by chunk from a spooled IMAP message
static void FileAppendDriverInit(STRING *s, void *data, unsigned long size);
static char FileAppendDriverNext(STRING *s);
static void FileAppendDriverSetpos(STRING *s, unsigned long i);
STRINGDRIVER FileAppendDriver =
{
FileAppendDriverInit,
FileAppendDriverNext,
FileAppendDriverSetpos
};
// STRINGFileStruct. Contains the data necessary for a STRING to open and read from a file
typedef struct STRINGFileStruct STRINGFileStruct, *STRINGFileStructPtr;
struct STRINGFileStruct
{
FSSpec spoolSpec; // spec of spooled message
UPtr buffer; // buffer to store partial fetch in
long bufferSize; // size of buffer
long bytesRead; // number of bytes read
};
/* functions defined herewithintotherefore */
//
// Mailbox Resync
//
OSErr CheckIMAPSettingsForPers(void);
OSErr SpecToUIDList(TOCHandle tocH, UIDNodeHandle *uidList, DeliveryNodeHandle node);
void MergeUidLists(UIDNodeHandle *localList, UIDNodeHandle *remoteList, UIDNodeHandle *updateList);
Boolean UIDFetchMessages(IMAPStreamPtr imapStream, MailboxNodeHandle mailboxInfo, UIDNodeHandle uidList, DeliveryNodeHandle delivery, Boolean isAutoCheck);
Handle UIDNodeList2Handle(UIDNodeHandle *uidList);
unsigned long GetLocalHighestUid(UIDNodeHandle list);
unsigned long GetUIDNodeCount(UIDNodeHandle list);
void ParseHeaderInMemory (MSumPtr sum, Handle headersH);
//
// Delivery Queue
//
DeliveryNodeHandle NewDeliveryNode(TOCHandle tocH);
void QueueDeliveryNode(DeliveryNodeHandle node);
void DequeueDeliveryNode(DeliveryNodeHandle node);
void AbortDeliveryNode(DeliveryNodeHandle node);
void ZapDeliveryNode(DeliveryNodeHandle *node);
Boolean SameTOC(TOCHandle toc1, TOCHandle toc2);
//
// Message Fetching
//
Boolean DoDownloadSingleMessage(IMAPStreamPtr imapStream, unsigned long uid, Boolean progress, Boolean attachmentsToo);
short OpenMessDestFile(MailboxNodeHandle mailboxInfo, unsigned long uid, FSSpecPtr spoolSpec);
void IMAPTempFileName(UIDVALIDITY uv, unsigned long uid, Str31 fileName);
OSErr UpdateIMAPTempFileIndex(IMAPStreamPtr imapStream, unsigned long uid, long oldFilePosition, long newFilePosition);
OSErr GetMessDestFilePos(IMAPStreamPtr imapStream, long *pos);
Boolean IsSpooledMessageFile(Str255 name, MailboxNodeHandle node);
Boolean DownloadSimpleBodyToSpoolFile(IMAPStreamPtr imapStream, unsigned long uid, IMAPBODY *body, char *section);
Boolean AppendBodyTextToSpoolFile(IMAPStreamPtr imapStream, unsigned long uid, char *section, long totalSize);
Boolean DownloadMultipartBodyToSpoolFile(IMAPStreamPtr imapStream, unsigned long uid, IMAPBODY *parentBody, char * parentSection, Boolean doingRelated);
void IMAPWriteBoundary(IMAPStreamPtr stream, IMAPBODY *body, unsigned long uid, char *section, Boolean outerBoundary);
Boolean PrefMakeAttachmentStub(long size);
Boolean GonnaGetThisAppleDouble(IMAPBODY *body);
Boolean WhackPString(UPtr line);
Boolean SpecialCaseDownload(IMAPBODY *bodyInQuestion);
Boolean DoUIDMarkAsDeleted(IMAPStreamPtr stream, Handle uids, Boolean undelete);
Boolean IMAPMessageBeingPreviewed(TOCHandle tocH, short sumNum);
void CleanUpResyncTaskErrors(MailboxNodeHandle mbox);
//
// Message Transfer
//
OSErr TransferMessageBetweenServers(PersHandle fromPersPers, IMAPStreamPtr fromStream, MailboxNodeHandle fromBox, TOCHandle fromTocH, PersHandle toPers, IMAPStreamPtr toStream, MailboxNodeHandle toBox, Handle uids, Boolean copy);
OSErr TransferMessageOnServer(PersHandle fromPers, IMAPStreamPtr fromStream, MailboxNodeHandle fromBox, TOCHandle fromTocH, MailboxNodeHandle toBox, Handle uids, Boolean copy);
Boolean CopyMessages(IMAPStreamPtr imapStream, MailboxNodeHandle mbox, Handle uids, Boolean copy, Boolean silent);
OSErr UpdateLocalSummaries(TOCHandle fromTocH, Handle uids, Boolean undelete, Boolean expunge, Boolean cleanupAttachments);
OSErr AppendSpoolFile(IMAPStreamPtr imapStream, IMAPAppendPtr spoolData);
Boolean NeedCleanUpAttachmentsAfterIMAPTransfer(TOCHandle tocH, short sumNum);
OSErr SpoolOnePopMessage(TOCHandle tocH, short sumNum, IMAPAppendPtr spoolData);
//
// UID list manipulation
//
short GenerateUIDString(Handle uids, short index, Str255 uidStr);
short GenerateUIDStringFromUIDNodeHandle(UIDNodeHandle uids, short index, unsigned long *firstUID, unsigned long *lastUID, Str255 uidStr);
int UIDListCompare(unsigned long *uid1, unsigned long *uid2);
void SwapUID(unsigned long *uid1, unsigned long *uid2);
void SortUIDListHandle(Handle toSort);
//
// Fancy Trash
//
OSErr FTMExpunge(IMAPStreamPtr imapStream, TOCHandle tocH);
Boolean IMAPWipeBox(TOCHandle toc, MailboxNodeHandle node);
//
// Stub File
//
void GetFilenameParameter(IMAPBODY *pBody, Str255 fileName);
Boolean SaveAttachmentStub(IMAPStreamPtr stream, unsigned long uid, char *section, IMAPBODY *body);
PStr BodyTypeCodeToPString (short type, PStr string);
void XMacHeaders(IMAPBODY *body, ResType *type, ResType *creator);
OSErr GetStubInfo(FSSpecPtr spec, AttachmentStubPtr stub);
//
// Attachment downloading and decoding
//
Boolean DownloadAttachmentToSpoolFile(IMAPStreamPtr imapStream, IMAPBODY *parentBody, unsigned long uid, char *parentSection, char *sectionToFetch);
Boolean SpoolFileToAttachment(MailboxNodeHandle mailbox, unsigned long uid, FSSpecPtr spoolSpec, FSSpecPtr attachSpec);
short NewScratchFile(FSSpecPtr where, FSSpecPtr scratchFile);
OSErr DeleteScratchFile(short ref, FSSpecPtr scratchFile);
void FindFirstAttachmentInSpec(FSSpecPtr inSpec, FSSpecPtr attachSpec);
OSErr IMAPRecvLine(TransStream stream, UPtr buffer, long *size);
unsigned long DownloadIMAPAttachments(FSSpecHandle attachments, MailboxNodeHandle mailbox, Boolean forceForeground);
void PrepareDownloadProgress(IMAPStreamPtr imapStream, long totalSize);
Boolean RedoIMAPAttachmentIcons(MyWindowPtr win, PETEHandle pte, FSSpecHandle attachSpecs);
//
// Searching
//
Boolean IMAPSearchServer(TOCHandle searchWin, PersHandle pers, BoxCountHandle boxesToSearch, Handle specsToSeach, IMAPSCHandle searchCriteria, Boolean matchAll, long firstUID, Boolean bAlreadyOnline);
void BuildSearchResults(DeliveryNodeHandle delivery, UIDNodeHandle results);
Boolean ReturnSearchHits(IMAPStreamPtr imapStream, DeliveryNodeHandle searchNode, FSSpecPtr spec, UIDNodeHandle *uids);
void BuildHeaderSearchString(Boolean anyHeader, Boolean anyRec, Str255 pHeaders);
void IMAPMailboxUIDRange(IMAPStreamPtr imapStream, unsigned long numMessages, unsigned long *first, unsigned long *last);
void IMAPMailboxChanged(MailboxNodeHandle mbox);
//
// Filtering
//
void IMAPMailboxPostProcess(MailboxNodeHandle tree, Boolean resync, Boolean expunge, Boolean search);
void RNtoR(Handle text);
Boolean IMAPFilteringWarnOffline(void);
MailboxNodeHandle GetNextWaitingMailboxNode(MailboxNodeHandle tree);
long IMAPCountUnfilteredMessages(MailboxNodeHandle mbox);
//
// Error reporting
//
OSErr IE(IMAPStreamPtr imapStream, short operation, short explanation, OSErr err);
//
// Offline Message Flag updates
//
OSErr PerformQueuedCommandsLo(PersHandle pers, MailboxNodeHandle tree, IMAPStreamPtr imapStream, Boolean progress);
OSErr UpdateMessFlags(IMAPStreamPtr imapStream, MailboxNodeHandle mailbox, Boolean progress);
Boolean LockMailboxNodeFlags(MailboxNodeHandle mailbox);
Boolean UnlockMailboxNodeFlags(MailboxNodeHandle mailbox);
Boolean SameFlags(LocalFlagChangePtr one, LocalFlagChangePtr two);
Boolean ProcessSimilarFlagChanges(IMAPStreamPtr imapStream, MailboxNodeHandle mailbox, Handle uidsToChange, LocalFlagChangePtr theFlags, Boolean progress, long totalFlags, long *processedFlags);
Boolean FastIMAPMessageDelete(TOCHandle tocH, Handle uids, Boolean bFTM);
#ifdef DEBUG
//
// Debug
//
void IMAPCollectFlagsFromTree(IMAPStreamPtr imapStream, MailboxNodeHandle *tree, short refN);
short OpenFlagsFile(void);
#endif
/* Let the fun begin ... */
/**********************************************************************
* CheckIMAPSettingsForPers - get the user's password if we need it and
* make sure the special mailboxes have been defined.
**********************************************************************/
OSErr CheckIMAPSettingsForPers(void)
{
OSErr err = noErr;
if (!CurPers) return fnfErr;
// prompt for the user's password if we need it
if ((!PrefIsSet(PREF_KERBEROS)) && !(*(*CurPers)->password))
err = PersFillPw(CurPers,0);
// make sure the user has selected the special mailboxes already
if (!EnsureSpecialMailboxes(CurPers))
err = userCancelled;
return err;
}
/**********************************************************************
* FetchNewMessages - fetch the new messages for a given mailbox.
* Assume CurPers is the personality the mailbox belongs to.
*
* If filter is true, run filters on this mailbox if it's approproate.
*
* Return pointer to the mailboxNode that we just modified,
* nil if we fail.
*
* Call IMAPDelivery() routine to get at results of download.
**********************************************************************/
MailboxNodeHandle FetchNewMessages(TOCHandle tocToSync, Boolean fetchFlags, Boolean sameThread, Boolean filter, Boolean isAutoCheck)
{
MailboxNodeHandle mailboxNode = nil;
OSErr err = noErr;
XferFlags flags;
DeliveryNodeHandle node;
IMAPTransferRec imapInfo;
PersHandle pers = nil, oldPers = CurPers;
FSSpec mailboxSpec;
// Something bad happened. We weren't told which mailbox to resync.
if (!tocToSync) return (nil);
// if there's already a delivery node set up for this TOC, then the resync is already underway.
if (tocToSync && FindNodeByToc(tocToSync))
return (nil);
mailboxSpec = (*tocToSync)->mailbox.spec;
// if we're in a thread, we must have already asked to go online.
ASSERT(InAThread()?!Offline:true);
// we must be o(nline ...
if (Offline && (sameThread || GoOnline())) return(nil);
// don't resync mailboxes if we're offline and not connected. Allow manual mailchecks to go through, though.
if (!IMAPCheckThreadRunning && (!AutoCheckOK())) return (nil);
// set up the structure that the summaries will be returned in.
node = NewDeliveryNode(tocToSync);
if (node)
{
// figure out who this mailbox belongs to
mailboxNode = TOCToMbox((*node)->toc);
pers = TOCToPers((*node)->toc);
// remove any old task errors
CleanUpResyncTaskErrors(mailboxNode);
// if this is the inbox, set the flag in the delivery node so filtering gets done later, unless we're told not to filter.
CurPers = pers;
if (filter && (mailboxNode == LocateInboxForPers(pers)) && !PrefIsSet(PREF_IMAP_NO_FILTER_INBOX))
(*node)->filter = true;
else
(*node)->filter = false;
CurPers = oldPers;
// and queue it in the global list of deliveries. It will be removed in the idle loop or the thread
QueueDeliveryNode(node);
if (PrefIsSet(PREF_THREADING_OFF) || !ThreadsAvailable() || sameThread)
{
mailboxNode = DoFetchNewMessages(&mailboxSpec, fetchFlags, isAutoCheck);
// Succeeded? Write potentially new mailbox info back to the mailboxes 'IMAP' resource.
if (mailboxNode && !sameThread)
{
err = WriteIMAPMailboxInfo(&mailboxSpec, mailboxNode);
}
}
else
{
// collect password now if we need it.
CurPers = pers;
err = CheckIMAPSettingsForPers();
// if we were given a password, set up the thread
if (err == noErr)
{
Zero(flags);
Zero(imapInfo);
SimpleMakeFSSpec(mailboxSpec.vRefNum, mailboxSpec.parID, mailboxSpec.name, &(imapInfo.targetSpec));
imapInfo.command = IMAPResyncTask;
err = SetupXferMailThread (false, false, true, false, flags, &imapInfo);
// if there was an error, the delivery node we created is now expendable.
if (err != noErr) AbortDeliveryNode(node);
if (KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN)) Zero((*CurPers)->password);
}
CurPers = oldPers;
}
}
else
WarnUser(MEM_ERR,MemError());
return (mailboxNode);
}
/**********************************************************************
* IMAPProcessMailboxes - fetch the new messages in all the mailboxes
* specified. Filters will be run on the appropriate mailboxes.
*
* Do this all over one single connection.
**********************************************************************/
OSErr IMAPProcessMailboxes(FSSpecHandle mailboxes, TaskKindEnum task)
{
OSErr err = noErr;
IMAPTransferRec imapInfo;
FSSpec spec;
short i,numSpecs;
short numResync = 0;
IMAPMailboxAttributes att;
FSSpecHandle resync = nil;
PersHandle pers = CurPers;
MailboxNodeHandle node;
XferFlags flags;
// must be the right kind of command
if ((task != IMAPMultResyncTask) && (task != IMAPMultExpungeTask))
return (paramErr);
// we must be online
if (Offline && GoOnline()) return(paramErr);
// must have some mailboxes to resync
if (!mailboxes || !*mailboxes) return (paramErr);
numSpecs = GetHandleSize(mailboxes)/sizeof(FSSpec);
resync = NuHandle(numSpecs * sizeof(FSSpec));
if (resync && (err=MemError())==noErr)
{
// Go through the mailboxes, and create a list of IMAP mailbox pointers to resync.
for (i = 0; i < numSpecs; i++)
{
spec = ((FSSpecPtr)(*mailboxes))[i];
// Get this mailbox' attributes
if (MailboxAttributes(&spec, &att))
{
// if this is a selectable IMAP mailbox, add it to the list of boxes to be resynced.
if (!(att.noSelect))
{
BMD(&spec, &((FSSpecPtr)(*resync))[numResync++], sizeof(FSSpec));
// also, collect the password for this personality if we need it.
LocateNodeBySpecInAllPersTrees(&spec, &node, &CurPers);
err = CheckIMAPSettingsForPers();
// and remove any old task errors
CleanUpResyncTaskErrors(node);
}
}
// else
// no attributes found - probably not an IMAP mailbox.
}
}
else
{
WarnUser(MEM_ERR, err);
ZapHandle(resync);
}
// done with the mailboxes handle.
ZapHandle(mailboxes);
// reset personality
CurPers = pers;
if (resync && numResync)
{
// shorten the resync handle
SetHandleSize(resync, numResync*sizeof(FSSpec));
// if there are any mailboxes to resynchronize, do it.
if (PrefIsSet(PREF_THREADING_OFF) || !ThreadsAvailable()) err = DoIMAPProcessMailboxes(resync, task) ? noErr : 1;
else
{
Zero(imapInfo);
Zero(flags);
imapInfo.command = task;
imapInfo.toResync = resync;
err = SetupXferMailThread (false, false, true, false, flags, &imapInfo);
}
// if we're using the keychain, go forget the passwords we just asked for
// unless we're in a thread! In that case, the password will get zapped when the thread ends.
if (!InAThread() && KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN))
{
for (pers = PersList; pers; pers = (*pers)->next)
Zero((*CurPers)->password);
}
}
else
{
ZapHandle(resync);
}
return err;
}
/**********************************************************************
* ResyncCurrentIMAPMailbox - fetch the new messages in the currently
* active and frontmost IMAP mailbox
**********************************************************************/
Boolean ResyncCurrentIMAPMailbox(void)
{
OSErr err = noErr;
IMAPTransferRec imapInfo;
FSSpec spec;
Boolean resyncMe = false;
IMAPMailboxAttributes att;
FSSpecHandle resync = nil;
PersHandle pers = CurPers;
MailboxNodeHandle node;
XferFlags flags;
WindowPtr winWP = MyFrontWindow();
MyWindowPtr win = GetWindowMyWindowPtr (winWP);
short kind = winWP?GetWindowKind(winWP) : 0;
TOCHandle tocH = nil;
// must have a mailbox to resync
if (!win || !(kind == MBOX_WIN)) return (false);
tocH = (TOCHandle)GetMyWindowPrivateData(win);
// must be an IMAP mailbox
if (!(*tocH)->imapTOC) return (false);
// we must be online
if (Offline && GoOnline()) return(true); // return as if the resync got started.
resync = NuHandle(sizeof(FSSpec));
if (resync && (err=MemError())==noErr)
{
spec = (*tocH)->mailbox.spec;
// Get this mailbox' attributes
if (MailboxAttributes(&spec, &att))
{
// if this is a selectable IMAP mailbox, add it to the list of boxes to be resynced.
if (!(att.noSelect))
{
BMD(&spec, *resync, sizeof(FSSpec));
// also, collect the password for this personality if we need it.
node = TOCToMbox(tocH);
CurPers = TOCToPers(tocH);
err = CheckIMAPSettingsForPers();
// and remove any old task errors
CleanUpResyncTaskErrors(node);
resyncMe = true; // we have something to resync
}
}
// else
// no attributes found - probably not an IMAP mailbox.
}
else
{
WarnUser(MEM_ERR, err);
ZapHandle(resync);
}
// reset personality
CurPers = pers;
if (resync && resyncMe)
{
if (PrefIsSet(PREF_THREADING_OFF) || !ThreadsAvailable()) err = DoIMAPProcessMailboxes(resync, IMAPMultResyncTask) ? noErr : 1;
else
{
Zero(imapInfo);
Zero(flags);
imapInfo.command = IMAPMultResyncTask;
imapInfo.toResync = resync;
err = SetupXferMailThread (false, false, true, false, flags, &imapInfo);
}
// if we're using the keychain, go forget the passwords we just asked for
if (KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN))
{
for (pers = PersList; pers; pers = (*pers)->next)
Zero((*CurPers)->password);
}
}
else
{
ZapHandle(resync);
}
return (true);
}
/**********************************************************************
* DoIMAPProcessMailboxes - Process the specified mailboxes. Assume the
* mailboxes specified are IMAP mailboxes that are SELECTable.
**********************************************************************/
Boolean DoIMAPProcessMailboxes(FSSpecHandle mailboxes, TaskKindEnum task)
{
Boolean result = true;
FSSpec specToProcess;
TOCHandle tocToProcess;
short numSpecs = GetHandleSize(mailboxes)/sizeof(FSSpec);
short i;
DeliveryNodeHandle node;
MailboxNodeHandle mailboxNode;
PersHandle pers = nil, oldPers = CurPers;
// loop through the mailboxes and process each, one by one.
for (i = 0; (i < numSpecs) && !CommandPeriod; i++)
{
// find the mailbox we want to resynchronize, open it if necessary
specToProcess = ((FSSpecPtr)(*mailboxes))[i];
tocToProcess = TOCBySpec(&specToProcess);
if (tocToProcess)
{
// don't let the toc go anywhere
IMAPTocHBusy(tocToProcess, true);
if (task == IMAPMultResyncTask)
{
// if there's already a delivery node set up for this TOC, then the resync is already underway.
if (tocToProcess && FindNodeByToc(tocToProcess))
{
IMAPTocHBusy(tocToProcess, false);
continue;
}
// set up the structure that the summaries will be returned in.
node = NewDeliveryNode(tocToProcess);
if (node)
{
// figure out who this mailbox belongs to
mailboxNode = TOCToMbox((*node)->toc);
pers = TOCToPers((*node)->toc);
// if this is the inbox, set the flag in the delivery node so filtering gets done later, unless we're told not to filter.
CurPers = pers;
if ((mailboxNode == LocateInboxForPers(pers)) && !PrefIsSet(PREF_IMAP_NO_FILTER_INBOX))
(*node)->filter = true;
else
(*node)->filter = false;
CurPers = oldPers;
// and queue it in the global list of deliveries. It will be removed in the idle loop or the thread
QueueDeliveryNode(node);
// resync the mailbox ...
DoFetchNewMessages(&specToProcess, true, false);
}
}
else if (task = IMAPMultExpungeTask)
{
DoExpungeMailboxLo(tocToProcess, true);
}
// the toc can be flushed now ...
IMAPTocHBusy(tocToProcess, false);
}
}
// we're done with this handle
ZapHandle(mailboxes);
return (result);
}
/**********************************************************************
* DoFetchNewMessages - fetch the new messages for a given mailbox.
* Assume CurPers is the personality the mailbox belongs to.
*
* Builds a DeliveryNode and sticks it in the list of deliveries.
* the deliveryNode is filled with:
*
* fills toAdd with summaries to be added to the toc
* fills toUpdate with summaries containing UID and new flags
* fills toDelete with UID only, or -1 if delete all.
*
* Return pointer to the mailboxNode that we just modified,
* nil if we fail.
**********************************************************************/
MailboxNodeHandle DoFetchNewMessages(FSSpecPtr mailboxSpec, Boolean fetchFlags, Boolean isAutoCheck)
{
OSErr err = noErr;
IMAPStreamPtr imapStream = nil;
UIDNodeHandle localList = nil, remoteList = nil, updateList = nil;
long noMessages = 0;
UIDVALIDITY newUIDValidity = 0;
MailboxNodeHandle mailboxInfo = nil;
PersHandle pers = nil;
PersHandle oldPers = CurPers;
Boolean redownloadAll = false;
unsigned long localUIDHighest;
Boolean result = false;
TOCHandle tocH = nil;
Str255 s;
Str63 m;
DeliveryNodeHandle delivery = nil;
SignedByte dState;
// find the toc for this mailbox, open it if we must.
tocH = TOCBySpec(mailboxSpec);
if (!tocH) return (nil);
// find the right deliveryNode
delivery = FindNodeByToc(tocH);
if (delivery && !(*delivery)->aborted)
{
// find the MailboxNode the spec describes
mailboxInfo = TOCToMbox(tocH);
pers = TOCToPers(tocH);
if (pers && mailboxInfo)
{
// this is used later to update the mailbox information
(*delivery)->mailbox = mailboxInfo;
// make sure this is an IMAP mailbox, and that it can be SELECTed.
if (((*mailboxInfo)->attributes & LATT_NOT_IMAP) || ((*mailboxInfo)->attributes & LATT_NOSELECT))
{
AbortDeliveryNode(delivery);
return (nil);
}
CurPers = pers;
// display some progress indicating which mailbox we're synching
LockMailboxNodeHandle(mailboxInfo);
PathToMailboxName((*mailboxInfo)->mailboxName, m, (*mailboxInfo)->delimiter);
UnlockMailboxNodeHandle(mailboxInfo);
ComposeRString(s,IMAP_RESYNC_MAILBOX,m);
PROGRESS_START;
PROGRESS_MESSAGE(kpTitle,s);
// display a little progress before we check the local cache. It could take a while on big mailboxes.
PROGRESS_MESSAGER(kpSubTitle,LOOK_MAIL);
// Those Progress calls yield, dammit. Make sure the mailbox resync wasn't cancelled by the
// user while they were being displayed. -jdboyd 5/24/02
if (!(*delivery)->aborted)
{
// build a list containing all the UIDs of the cached messages
err = SpecToUIDList(tocH, &localList, delivery);
if (err == noErr)
{
// and remember the highest UID we have locally.
localUIDHighest = GetLocalHighestUid(localList);
// create a new IMAP stream to talk through
imapStream = GetIMAPConnection(IMAPResyncTask, CAN_PROGRESS);
if (!imapStream)
{
PROGRESS_END;
CurPers = oldPers;
AbortDeliveryNode(delivery);
return (nil);
}
// log into the server and SELECT the mailbox
PROGRESS_MESSAGER(kpSubTitle,IMAP_LOGGING_IN);
LockMailboxNodeHandle(mailboxInfo);
if (IMAPOpenMailbox(imapStream, (*mailboxInfo)->mailboxName,false))
{
// now look for some mail
PROGRESS_MESSAGER(kpSubTitle,LOOK_MAIL);
// figure out what needs to be downloaded.
noMessages = GetMessageCount(imapStream);
newUIDValidity = UIDValidity(imapStream);
// check to make sure we haven't aborted ...
if (!(*delivery)->aborted)
{
// if the remote mailbox contains no messages, updating the cache is as easy as deleteing everything.
if (noMessages == 0)
{
// tell caller to remove everything from the local cahce
(*delivery)->td = MSUM_DELETE_ALL;
err = noErr;
// Audit the fact that a mailcheck started and finished, but that nothing was fetched.
AuditCheckStart(++gCheckSessionID,(*CurPersSafe)->persId,isAutoCheck);
AuditCheckDone(gCheckSessionID,0,0);
}
// we have some more complicated synching to do
else
{
// if we've seen this mailbox before and the UIDVALIDITY of the mailbox has changed,
// we'll have to download everything.
if (((*mailboxInfo)->uidValidity != 0) && (newUIDValidity != (*mailboxInfo)->uidValidity)) redownloadAll = true;
// if we're redownloading everything, the we must fetch flags, too.
if (redownloadAll) fetchFlags = true;
// if fetchFlags is true, download flags for all of the messages
if (fetchFlags) result = FetchAllFlags(imapStream, &remoteList);
else // otherwise, just pay attention to new messages
{
unsigned long serverUIDHighest = UIDFetchLastUid(imapStream);
Str255 seq;
if (serverUIDHighest > localUIDHighest)
{
WriteZero(seq, sizeof(seq));
ComposeString(seq, "\p%lu:*", localUIDHighest + 1);
result = FetchFlags(imapStream, seq + 1, &remoteList);
}
}
if (remoteList && result)
{
// if we're redownloading everything, zap our local cache
if (redownloadAll)
{
// local UID list should be empty
UID_LL_Zap(&localList);
// tell caller to remove everything from the local cache
(*delivery)->td = MSUM_DELETE_ALL;
}
else
{
if (!fetchFlags)
{
// if we didn't fetch new uid's, then we're not going to change
// the local cache at all. Delete the local list so we don't
// end up deleting anything locally.
UID_LL_Zap(&localList);
// tell caller that nothing is to be removed from the local cache
(*delivery)->td = nil;
}
else
{
// Merge the two lists.
// The three lists will end up in the following states:
// 1. localList will contain uid's no longer on the server ONLY.
// 2. remoteList will contain UIDs of messages that need to be fetched
// 3. updateList will contain new flags for local messages still on the server.
MergeUidLists(&localList, &remoteList, &updateList);
}
}
// Update mailbox info.
(*mailboxInfo)->uidValidity = newUIDValidity;
// Actually go fetch the new messages.
UIDFetchMessages(imapStream, mailboxInfo, remoteList, delivery, isAutoCheck);
}
else
{
// an error occurred building the remoteList.
// abort the resync
AbortDeliveryNode(delivery);
}
}
if (!(*delivery)->aborted)
{
dState = HGetState((Handle)delivery);
LDRef(delivery);
// create the list of summaries to be updated, destroy the updateList
if (updateList) (*delivery)->tu = UIDNodeList2Handle(&updateList);
// create the list of summaries to be deleted, destroy the localList
if (localList) (*delivery)->td = UIDNodeList2Handle(&localList);
HSetState((Handle)delivery, dState);
// are there summaries to be added?
if ((*delivery)->ta)
IMAPMailboxChanged(mailboxInfo);
}
}
else
{
if (!(*delivery)->aborted) err = IMAPError(kIMAPResync, kIMAPSelectMailboxErr, errIMAPSelectMailbox);
else err = userCanceledErr; // we aborted. Don't display an error message.
}
}
else // failed to open the mailbox.
err = IE(imapStream, kIMAPResync, kIMAPSelectMailboxErr, errIMAPSelectMailbox);
UnlockMailboxNodeHandle(mailboxInfo);
PROGRESS_MESSAGER(kpSubTitle,CLEANUP_CONNECTION);
// Zap the remote list. toAdd was created in UIDFetchMessages.
UID_LL_Zap(&remoteList);
// close down and clean up
CleanupConnection(&imapStream);
}
else
{
// an error occurred reading in the local cache.
AbortDeliveryNode(delivery);
if (err != userCanceledErr) IMAPError(kIMAPResync, kIMAPMemErr, errIMAPOutOfMemory);
}
}
// complete or abort the delivery ...
if ((*delivery)->aborted)
{
// remember that the user cancelled. The delievery node will be removed later.
err = userCancelled;
}
else
{
// we're really, truely done now.
(*delivery)->finished = true;
}
CurPers = oldPers;
PROGRESS_END;
}
else // dooesn't seem to be an IMAP personality
{
err = IMAPError(kIMAPResync, kIMAPNotIMAPMailboxErr, errNotIMAPMailboxErr);
AbortDeliveryNode(delivery);
}
}
return (err==noErr?mailboxInfo:nil);
}
/**********************************************************************
* SpecToUIDList - given an FSSpec, return a list of all the UIDs in
* that mailbox.
*
* Since we're looking at every message in the mailbox anyway, process
* junk if this is an IMAP junk mailbox as well. That is, assign all
* scoreless messages a score. They've had ample time to be scored.
**********************************************************************/
OSErr SpecToUIDList(TOCHandle tocH, UIDNodeHandle *list, DeliveryNodeHandle deliveryNode)
{
OSErr err = noErr;
UIDNodeHandle node = nil;
MailboxNodeHandle mBox;
Boolean bIsJunkMbox = false;
short defaultScore;
short sumNum;
TOCHandle hidTocH;
// make sure we were passed a toc
if (tocH == nil) return (nil);
// find the mailbox associated with this TOC
mBox = TOCToMbox(tocH);
if (mBox)
{
// turn off sorting for this mailbox while we count the local messages
SetIMAPMailboxNeeds(mBox, kNeedsSortLock, true);
// take care of unscored messages if this is an IMAP junk mailbox
if ((bIsJunkMbox = IsIMAPJunkMailbox(mBox))==true)
defaultScore = GetRLong(IMAP_DEFAULT_JUNK_SCORE);
}
//iterate through the toc
for (sumNum = 0; (sumNum < (*tocH)->count) && (err==noErr); sumNum++)
{
CycleBalls();
// stop if the mailbox went away
// since the CycleBalls() call was added above, it's been possible to have the mailbox
// get closed and the TOC flushed out from under us. If we notice the TOC has vanished,
// stop. jdboyd 03/22/02
if ((*deliveryNode)->aborted)
{
break;
}
// assign junk score if this is an unscored message in an IMAP junk mailbox
if (bIsJunkMbox)
{
if ((*tocH)->sums[sumNum].spamBecause == 0)
{
(*tocH)->sums[sumNum].spamBecause = JUNK_BECAUSE_IMAP_SUCKS;
(*tocH)->sums[sumNum].spamScore = defaultScore;
}
}
node = NewZH(UIDNode);
if (node)
{
(*node)->uid = (*tocH)->sums[sumNum].uidHash;
// ordered insert this node into the list
UID_LL_OrderedInsert(list, &node, false);
}
else
{
err = MemError();
}
}
// are the deleted messages hidden?
hidTocH = GetHiddenCacheMailbox(mBox, false, false);
if (hidTocH)
{
// then consider the messages in the hidden cache as well.
IMAPTocHBusy(hidTocH, true);
for (sumNum = 0; (sumNum < (*hidTocH)->count) && (err==noErr); sumNum++)
{
CycleBalls();
node = NewZH(UIDNode);
if (node)
{
(*node)->uid = (*hidTocH)->sums[sumNum].uidHash;
// ordered insert this node into the list
UID_LL_OrderedInsert(list, &node, false);
}
else
{
err = MemError();
}
}
IMAPTocHBusy(hidTocH, false);
}
// restore sorting for this mailbox
if (mBox) SetIMAPMailboxNeeds(mBox, kNeedsSortLock, false);
return (err);
}
/**********************************************************************
* UID_LL_Zap - destroy a list node by node.
**********************************************************************/
void UID_LL_Zap(UIDNodeHandle *list)
{
UIDNodeHandle node;
while (node=*list)
{
LL_Remove(*list,node,(UIDNodeHandle));
ZapHandle(node);
}
*list = nil;
}
/**********************************************************************
* GetUIDNodeCount - return the number of nodes in a list
**********************************************************************/
unsigned long GetUIDNodeCount(UIDNodeHandle list)
{
UIDNodeHandle scan = list;
unsigned long count = 0;
while (scan)
{
count++;
scan = (*scan)->next;
}
return (count);
}
/**********************************************************************
* GetLocalHighestUid - return the highest UID in the list
**********************************************************************/
unsigned long GetLocalHighestUid(UIDNodeHandle list)
{
UIDNodeHandle scan = list;
if (list) LL_Last(list,scan);
return (scan?(*scan)->uid:0);
}
/**********************************************************************
* UID_LL_OrderedInsert - insert a UIDNode into a UID list where it
* belongs. This will build an UID list ordered by UID.
**********************************************************************/
void UID_LL_OrderedInsert(UIDNodeHandle *head, UIDNodeHandle *item, Boolean isNew)
{
UIDNodeHandle t = *head, p = 0;
if (head && *head)
{
// find the first node with a smaller UID than the one we're inserting
while (t && (*t)->uid < (**item)->uid)
t = (*t)->next;
// if this node has the same uid, replace it with this new node.
if ((t) && ((*t)->uid == (**item)->uid))
{
(*t)->l_seen = (**item)->l_seen;
(*t)->l_deleted = (**item)->l_deleted;
(*t)->l_flagged = (**item)->l_flagged;
(*t)->l_answered = (**item)->l_answered;
(*t)->l_draft = (**item)->l_draft;
(*t)->l_recent = (**item)->l_recent;
(*t)->l_sent = (**item)->l_sent;
ZapHandle(*item);
}
// otherwise insert it ahead of this node
else
{
LL_Parent(*head, t, p);
// if this node has a parent, make it point to the new item.
if (p) (*p)->next = *item;
// if it doesn't, it's the head. Make the head point here now.
else *head = *item;
(**item)->next = t;
}
if (t && *t) (*t)->l_IsNew = isNew;
}
else
*head = *item;
}
/**********************************************************************
* MergeUidLists - Merge two UID lists, creating a third.
* - localList will contain uid's no longer on the server ONLY.
* - remoteList will contain UIDs of messages that need to be fetched
* - updateList will contain new flags for local messages still on
* the server
**********************************************************************/
void MergeUidLists(UIDNodeHandle *localList, UIDNodeHandle *remoteList, UIDNodeHandle *updateList)
{
UIDNodeHandle localScan = nil;
UIDNodeHandle remoteScan = nil;
UIDNodeHandle nextLocal = nil, nextRemote = nil;
// must have been passed three UIDNodeHandles ...
if (!localList || !remoteList || !updateList) return;
// start with empty update list ...
if (*updateList!=nil) UID_LL_Zap(updateList);
// look at each node in the local list.
localScan = *localList;
while (localScan)
{
CycleBalls();
nextLocal = (*localScan)->next;
// compare this message with each remote message
remoteScan = *remoteList;
while (remoteScan && localScan && ((*remoteScan)->uid<=(*localScan)->uid))
{
nextRemote = (*remoteScan)->next;
// this message is still on the server
if ((*remoteScan)->uid==(*localScan)->uid)
{
// remove this node from the localList and delete it
LL_Remove(*localList, localScan, (UIDNodeHandle));
ZapHandle(localScan);
localScan = nil;
// remove this node from the remoteList
LL_Remove(*remoteList, remoteScan, (UIDNodeHandle));
// add the node from the remoteList to the updateList.
(*remoteScan)->next = nil;
UID_LL_OrderedInsert(updateList, &remoteScan, false);
}
// move on the next remote message
remoteScan = nextRemote;
}
// move on to next local message
localScan = nextLocal;
}
}
/**********************************************************************
* UIDFetchMessages - fetch the messages in uidList.
* now does this in chunks for better performance
**********************************************************************/
Boolean UIDFetchMessages(IMAPStreamPtr imapStream, MailboxNodeHandle mailboxInfo, UIDNodeHandle uidList, DeliveryNodeHandle delivery, Boolean isAutoCheck)
{
Boolean result = true;
unsigned long initialMessageCount, remainingCount;
UIDNodeHandle node = nil;
OSErr err = noErr;
FSSpec spoolSpec;
long totalReceived;
long failed = 0;
Str255 pShortHeaderFields;
Str255 sequence;
long i,n, fetchedMessages;
unsigned long firstUID, lastUID;
Boolean fetchFullMessages = PrefIsSet(PREF_IMAP_FULL_MESSAGE) || PrefIsSet(PREF_IMAP_FULL_MESSAGE_AND_ATTACHMENTS);
// audit the fact an IMAP mailcheck is about to happen ...
AuditCheckStart(++gCheckSessionID,(*CurPersSafe)->persId,isAutoCheck);
StartStreamAudit(imapStream->mailStream->transStream, kAuditBytesReceived);
// these are the headers we'll be fetching ...
Zero(pShortHeaderFields);
GetRString(pShortHeaderFields, IMAP_SHORT_HEADER_FIELDS);
// figure out how many messages we're going to be downloading
remainingCount = initialMessageCount = GetUIDNodeCount(uidList);
fetchedMessages = 0;
if (initialMessageCount > 0)
{
// cough up some progress information at this point
PROGRESS_MESSAGER(kpSubTitle,LEFT_TO_TRANSFER);
PROGRESS_BAR(remainingCount/initialMessageCount,initialMessageCount,nil,nil,nil);
// if we're doing a full download, create the spool file now.
if ((PrefIsSet(PREF_IMAP_FULL_MESSAGE) || PrefIsSet(PREF_IMAP_FULL_MESSAGE_AND_ATTACHMENTS)) && uidList)
{
imapStream->mailStream->refN = OpenMessDestFile(mailboxInfo, (*uidList)->uid, &spoolSpec);
if (imapStream->mailStream->refN == -1) result = false;
}
// set up the IMAP stream to do a chunked header fetch
imapStream->mailStream->fUIDResults = uidList;
imapStream->mailStream->delivery = (Handle)delivery;
imapStream->mailStream->chunkHeaders = true;
// set up the IMAP stream to display a little progress
imapStream->mailStream->showProgress = !fetchFullMessages;
imapStream->mailStream->lastProgress = 0;
imapStream->mailStream->currentTransfer = imapStream->mailStream->totalTransfer = initialMessageCount;
// tell the IMAP stream what mailbox we're operating on
imapStream->mbox = mailboxInfo;
// now go fetch them
for (i = 0; i < initialMessageCount; i++)
{
// if we were aborted, stop the resync now.
if ((*delivery)->aborted)
{
result = false;
break;
}
// build the range string for the command ...
if (PrefIsSet(PREF_IMAP_DONT_USE_UID_RANGE))
{
node = uidList;
for (n = 0; n < i; n++) node = (*node)->next;
firstUID = lastUID = (*node)->uid;
sprintf (sequence,"%lu",(*node)->uid);
}
else
i = GenerateUIDStringFromUIDNodeHandle(uidList, i, &firstUID, &lastUID, &sequence);
if (result=UIDFetchRFC822HeaderFields(imapStream, -1, sequence, pShortHeaderFields+1))
{
// download the messages as well, if the user is a few fries short of a happy meal
if (fetchFullMessages)
{
for (n = firstUID; (n <= lastUID) && (!(*delivery)->aborted) && !CommandPeriod; n++)
{
// increment the progress bar now
PROGRESS_BAR(((100 * fetchedMessages++)/initialMessageCount),remainingCount-(n - firstUID),nil,nil,nil);
imapStream->mailStream->chunkHeaders = false;
result = DoDownloadSingleMessage(imapStream, n, false, PrefIsSet(PREF_IMAP_FULL_MESSAGE_AND_ATTACHMENTS));
imapStream->mailStream->chunkHeaders = true;
}
}
}
else
{
// We failed to fetch one message. Remember to warn the user, but continue on with the download.
failed++;
}
if (CommandPeriod)
{
AbortDeliveryNode(delivery);
IMAPRudeConnectionClose(imapStream);
}
remainingCount = initialMessageCount - i;
}
}
// else
// no messages to download. We're done.
done:
// Audit the number of messages we received with this check.
totalReceived = initialMessageCount-remainingCount;
AuditCheckDone(gCheckSessionID,totalReceived,totalReceived ? ReportStreamAudit(imapStream->mailStream->transStream) : 0);
// display error
if (failed) IE(imapStream, kIMAPCompleteResync, 0, errIMAPOneDownloadFailed);
// reset these bits of the MAILSTREAM
imapStream->mailStream->chunkHeaders = false;
imapStream->mailStream->fUIDResults = imapStream->mailStream->delivery = nil;
imapStream->mailStream->showProgress = false;
imapStream->mailStream->lastProgress = imapStream->mailStream->currentTransfer = imapStream->mailStream->totalTransfer = 0;
imapStream->mbox = NULL;
return (result);
}
/**********************************************************************
* SaveMinimalHeader - this is called each time a minimal header is
* fetched from the IMAP server
**********************************************************************/
OSErr SaveMinimalHeader(MAILSTREAM *stream)
{
OSErr err = noErr;
UIDNodeHandle node = stream->fUIDResults;
DeliveryNodeHandle delivery = (DeliveryNodeHandle)(stream->delivery);
MSumType sum;
long offset = 0;
SignedByte dState;
Str255 buf;
long ticks;
if (node && delivery)
{
// find the node this header belongs to
while (node)
{
if ((*node)->uid == stream->headerUID) break;
node = (*node)->next;
}
// found it. Now parse the headers and stick them into the delivery node
if (node && (*node)->uid)
{
// display a little progress
if (stream->showProgress)
{
if (((ticks=TickCount()) - (stream->lastProgress) > 60) || (stream->currentTransfer)<=1)
{
PROGRESS_BAR(100-(((stream->currentTransfer))*100/(stream->totalTransfer)),(stream->currentTransfer),nil,nil,nil);
stream->lastProgress = ticks;
}
}
// clear the summary about to be added
Zero(sum);
// if this message has been marked as a draft, treat it as an outgoing message
if ((*node)->l_sent) sum.opts |= OPT_IMAP_SENT;
// fill in the summary with the header information we just got
ParseHeaderInMemory(&sum,stream->fNetData);
ZapHandle(stream->fNetData);
// Provide a little info on what we're doing
if (PrefIsSet(PREF_IMAP_VERBOSE_RESYNC))
{
PCopy(buf,sum.from);
*buf = MIN(*buf,31);
PCatC(buf,',');
PCatC(buf,' ');
PSCat(buf,sum.subj);
PROGRESS_MESSAGE(kpMessage,buf);
}
// grab the uid of the message we're downloading
sum.uidHash = (*node)->uid;
// if the /seen flag is set, mark this message as read.
if ((*node)->l_seen) sum.state = READ;
// if this message has been answered, mark it as replied.
if ((*node)->l_answered) sum.state = REPLIED;
// if this message has been marked as deleted, flag it as deleted
if ((*node)->l_deleted) sum.opts |= OPT_DELETED;
// if this message is flagged, mark it with the flagged label
if ((*node)->l_flagged)
{
short color = GetRLong(IMAP_FLAGGED_LABEL);
if (color > 0 && color < 16)
{
sum.flags &= ~(FLAG_HUE1|FLAG_HUE2|FLAG_HUE3|FLAG_HUE4);
sum.flags |= (color<<14);
}
}
// if this message is being put into a mailbox that will be filtered, flag it.
if ((*delivery)->filter) sum.flags |= FLAG_UNFILTERED;
// remember the size of the message as well.
if ((sum.length=(*node)->size)==0) sum.length = 1 K;
// flag this summary so we know to go download the message later
sum.offset = imapNeedToDownload;
// remember when this summary arrived ...
sum.arrivalSeconds = GMTDateTime();
// spam score
sum.spamScore = -1;
sum.spamBecause = 0;
// stick the summary into the sum list
dState = HGetState((Handle)delivery);
LDRef(delivery);
if ((*delivery)->ta)
{
offset = GetHandleSize((*delivery)->ta);
SetHandleBig_((*delivery)->ta,offset + sizeof(MSumType));
if (err=MemError())
{
WarnUser(MEM_ERR, err);
HSetState((Handle)delivery, dState);
goto done;
}
}
else
{
(*delivery)->ta = NuHTempBetter(sizeof(MSumType));
if (!(*delivery)->ta)
{
WarnUser(MEM_ERR, MemError());
HSetState((Handle)delivery, dState);
goto done;
}
}
BMD(&sum,*((*delivery)->ta)+offset,sizeof(MSumType));
HSetState((Handle)delivery, dState);
}
}
// we processed one minimal header
stream->currentTransfer--;
done:
return (err);
}
/**********************************************************************
* ParseHeaderInMemory - Parse headers in a memory buffer and fill in
* the summary info into sum.
* The headers are in lines separated by "\r\n".
*
* The header buffer may contain any number of header lines. This
* function can be used to parse a minimal set of headers or the
* full set.
**********************************************************************/
void ParseHeaderInMemory (MSumPtr sum, Handle headersH)
{
char buf[512];
char *scan = 0;
Str255 headerName;
Str255 toLine;
Str255 scratch;
unsigned long secs;
long origZone;
long len;
Boolean out = (sum->opts&OPT_IMAP_SENT)!=0;
Boolean foundMultipartMixed = false;
Str255 boundary;
char *headers = nil;
uLong addrHash = kNoMessageId;
// We need a summary to continue
if (!sum) return;
// find the headers
if (headersH) headers = LDRef(headersH);
else GetRString(sum->from,UNKNOWN_SENDER);
// fill in some of the fields
sum->state = UNREAD;
sum->tableId = DEFAULT_TABLE;
sum->persId = sum->popPersId = (*CurPersSafe)->persId;
if (PrefIsSet(PREF_SHOW_ALL)) sum->flags |= FLAG_SHOW_ALL;
if(HasUnicode()) sum->flags |= FLAG_UTF8;
sum->offset = 0;
sum->length = 0;
// figre out some header names
Zero(boundary);
GetRString(boundary,AttributeStrn+aBoundary);
// now parse the headers headers points to.
while (headers && *headers)
{
// BUG: We must search for headers spanning multiple lines!!!
buf[0] = '\0';
scan = strstr(headers, "\r\n");
// Did we get a line?
if (scan)
{
if (scan == headers)
{
// Blank line. This is the end of the header.
headers = NULL;
buf[0] = '\0';
break;
}
else
{
// Don't overfill buffer.
len = (scan - headers);
if ((len + 2) < sizeof (buf))
{
strncpy (buf, headers, len);
buf[len] = '\0';
}
// Skip over \r\n
headers = scan + 2;
}
}
else
{
// Perhaps a last line not terminated by "\r\n"?
if (strlen(headers) < sizeof(buf))
strcpy (buf, headers);
// No more lines.
headers = NULL;
}
// Did we get anything in our buffer?
if (*buf)
{
// Date:
if (!sum->seconds && !pstrincmp(buf,GetRString(headerName,HeaderStrn+DATE_HEAD),headerName[0]))
{
CopyHeaderLine(toLine,sizeof(toLine),buf);
if (secs=BeautifyDate(toLine,&origZone))
{
PtrTimeStamp(sum,secs,origZone);
}
}
// To:
else if (out && !*(sum->from) && !pstrincmp(buf,GetRString(headerName,HeaderStrn+TO_HEAD),headerName[0]))
{
CopyHeaderLine(toLine,sizeof(toLine),buf);
if(sum->flags & FLAG_UTF8) HeaderToUTF8(toLine);
else WhackPString(toLine);
BeautifyFrom(toLine);
PSCopy(sum->from,toLine);
if(sum->flags & FLAG_UTF8) TrimUTF8(sum->from);
}
// From:
else if (!out && !*(sum->from) && !pstrincmp(buf,GetRString(headerName,HeaderStrn+FROM_HEAD),headerName[0]))
{
CopyHeaderLine(toLine,sizeof(toLine),buf);
if(sum->flags & FLAG_UTF8) HeaderToUTF8(toLine);
else WhackPString(toLine);
BeautifyFrom(toLine);
PSCopy(sum->from,toLine);
if(sum->flags & FLAG_UTF8) TrimUTF8(sum->from);
// fromHash
CopyHeaderLine(toLine,sizeof(toLine),buf);
ShortAddr(scratch,toLine);
MyLowerStr(scratch);
addrHash = Hash(scratch);
}
// Subject:
else if (!*(sum->subj) && !pstrincmp(buf, GetRString(headerName,HeaderStrn+SUBJ_HEAD),headerName[0]))
{
CopyHeaderLine(toLine,sizeof(toLine),buf);
if(sum->flags & FLAG_UTF8) HeaderToUTF8(toLine);
else WhackPString(toLine);
TrimWhite(toLine);
PSCopy(sum->subj,toLine);
if(sum->flags & FLAG_UTF8) TrimUTF8(sum->subj);
}
// Priority:
else if (!pstrincmp(buf,GetRString(headerName,HeaderStrn+PRIORITY_HEAD),headerName[0]))
{
sum->priority = sum->origPriority = Display2Prior(Atoi(buf+headerName[0]));
}
// Content-type:
else if (!pstrincmp(buf, GetRString(headerName,InterestHeadStrn+hContentType), headerName[0]))
{
// enriched text?
GetRString(scratch,MIME_RICHTEXT);
scratch[scratch[0]+1] = nil;
if (strstr(buf, scratch+1))
sum->flags |= FLAG_RICH;
// html or x-html
GetRString(scratch,HTMLTagsStrn+htmlTag);
if (strstr(buf, scratch+1))
sum->opts |= OPT_HTML;
// multipart
GetRString(scratch,MIME_MULTIPART);
scratch[scratch[0]+1] = nil;
if (strstr(buf, scratch+1))
{
// mixed
GetRString(scratch,MIME_MIXED);
scratch[scratch[0]+1] = nil;
if (strstr(buf, scratch+1))
{
// remember that we saw multipart/mixed
foundMultipartMixed = true;
// with a boundary ... assume this message has an attachment
if (strstr(buf, boundary+1)) sum->flags |= FLAG_HAS_ATT;
}
}
}
// boundary, on it's own line
else if (strstr(buf, boundary+1))
{
// if we've come across multipart/mixed already,
// and we find a boundary on it's own line, there's probably and attachment
if (foundMultipartMixed) sum->flags |= FLAG_HAS_ATT;
}
} // End parsing this line.
} // End While.
// set the fromHash
sum->fromHash = addrHash;
if (headersH) UL(headersH);
}
// defined in lex822.c.
Boolean Fix1342(UPtr chars,long *len);
/************************************************************************
* WhackPString - translate RFC 1342 stuff in a pascal string, and
* transliterate.
************************************************************************/
Boolean WhackPString(UPtr line)
{
Boolean result = true;
long len = line[0];
// translate RFC 1342 stuff
result = Fix1342(line+1, &len);
if (result) line[0] = len;
// Transliterate the string
if (line[0]) TransLitString(line);
return (result);
}
/************************************************************************
* pstrincmp - compare two strings, one pascal, one c, ignore case
************************************************************************/
int pstrincmp(CStr cs,UPtr ps,short n)
{
register c1, c2;
++ps; // skip over length of PString
for (c1= *ps, c2= *cs; n--; c1 = *++ps, c2= *++cs)
{
if (c1-c2)
{
if (isupper(c1)) c1=tolower(c1);
if (isupper(c2)) c2=tolower(c2);
if (c1-c2) return (c1-c2);
}
}
return(0);
}
/************************************************************************
* UIDNodeList2Handle - given a list of UIDNodes, convert it to
* a handle packed with summaries. Used to create the toDelete and
* toUpdate lists, since they only need information from the FetchFlags
* call.
* Note; the UIDNodeList is deleted.
************************************************************************/
Handle UIDNodeList2Handle(UIDNodeHandle *uidList)
{
UIDNodeHandle scan = *uidList;
Accumulator a;
MSumType sum;
Handle h = nil;
// nothing to convert.
if (!uidList || !*uidList) return (nil);
if (AccuInit(&a) == noErr)
{
while (scan = *uidList)
{
// clear sum
Zero(sum);
// uid
sum.uidHash = (*scan)->uid;
// flags
sum.state = UNREAD;
if ((*scan)->l_seen) sum.state = READ;
if ((*scan)->l_answered) sum.state = REPLIED;
if ((*scan)->l_deleted) sum.opts |= OPT_DELETED;
if ((*scan)->l_sent) sum.opts |= OPT_IMAP_SENT;
// if this message is flagged, mark it with the flagged label
if ((*scan)->l_flagged)
{
short color = GetRLong(IMAP_FLAGGED_LABEL);
if (color > 0 && color < 16)
{
sum.flags &= ~(FLAG_HUE1|FLAG_HUE2|FLAG_HUE3|FLAG_HUE4);
sum.flags |= (color<<14);
}
}
// add this sum to the Accumulator
AccuAddPtr(&a, &sum, sizeof(MSumType));
// remove this UIDNode and zap it
LL_Remove(*uidList, scan, (UIDNodeHandle));
ZapHandle(scan);
}
AccuTrim(&a);
h = a.data;
a.data = nil;
AccuZap(a);
}
return (h);
}
/************************************************************************
* IMAPDelivery - return true if there are messages waiting to be
* delivered into a specified mailbox.
*
* Fill toAdd, toUpdate, and toDelete with summaries to be added.
* Set mbox to the MailboxNodeHandle of the mailbox we updated when finished.
************************************************************************/
Boolean IMAPDelivery(TOCHandle inToc, Handle *toAdd, Handle *toUpdate, Handle *toDelete, Handle *toCopy, Boolean *filter, IMAPSResultHandle *results, MailboxNodeHandle *mbox, Boolean *checkAttachments)
{
Boolean messagesWaiting= false;
DeliveryNodeHandle node = gDeliveryQueue;
DeliveryNodeHandle next;
PersHandle oldPers = CurPers;
SignedByte dState;
if (filter) *filter = false;
*mbox = nil;
// loop through list of deliveries
while (node)
{
dState = HGetState((Handle)node);
LDRef(node);
if (((*node)->aborted == false) && (SameTOC(inToc, (*node)->toc)))
{
HSetState((Handle)node, dState);
// grab the lists of summaries built so far
*toAdd = (*node)->ta;
*toUpdate = (*node)->tu;
*toDelete = (*node)->td;
*toCopy = (*node)->tc;
*results = (*node)->results;
if (checkAttachments) *checkAttachments = (*node)->cleanupAttachments;
// return true if there are messages waiting
if ((*toAdd) || (*toUpdate) || (*toDelete) || (*toCopy) || (*results)) messagesWaiting = true;
// remember if messages are going to be added to the mailbox.
if (*toAdd)
SetIMAPMailboxNeeds((*node)->mailbox, kNeedsSelect, true);
// set the lists back to nil
(*node)->ta = (*node)->tu = (*node)->td = (*node)->tc = (*node)->results = nil;
// if we're finished with this mailbox, remove the delivery node.
if ((*node)->finished)
{
// Set the filter flag for foreground filtering
if (filter)
*filter = (*node)->filter;
// Mark the mailbox as needing to be filtered for background filtering
if ((*node)->filter && (*node)->mailbox && !PrefIsSet(PREF_FOREGROUND_IMAP_FILTERING))
SetIMAPMailboxNeeds((*node)->mailbox, kNeedsFilter, true);
*mbox = (*node)->mailbox;
DequeueDeliveryNode(node);
ZapDeliveryNode(&node);
messagesWaiting = true;
// set the messages waiting flag in the toc if we have to.
if (*mbox!=nil)
{
CurPers = MailboxNodeToPers(*mbox);
if (CurPers)
{
if (PrefIsSet(PREF_IMAP_FULL_MESSAGE) || PrefIsSet(PREF_IMAP_FULL_MESSAGE_AND_ATTACHMENTS))
{
(*inToc)->imapMessagesWaiting = 1;
}
}
CurPers = oldPers;
}
else // no mailbox. This was a search.
{
if ((*inToc)->virtualTOC) *mbox = SEARCH_WINDOW;
}
// Moodmail? Rescan this mailbox.
(*inToc)->analScanned = false;
}
break;
}
else HSetState((Handle)node, dState);
// clean up the delivery queue of aborted nodes
// if we're sure they're not in use by a thread somewere.
if ((*node)->aborted && !GetNumBackgroundThreads())
{
next = (*node)->next;
DequeueDeliveryNode(node);
ZapDeliveryNode(&node);
node = next;
}
else
node = (*node)->next;
}
return (messagesWaiting);
}
/************************************************************************
* IMAPAbortResync - the mailbox is closing. Take care of cleaning up
* after we IMAPed all over the place.
*
* Call the from a thread an be very sorry
************************************************************************/
void IMAPAbortResync(TOCHandle toc)
{
DeliveryNodeHandle node = gDeliveryQueue;
SignedByte state;
// Is there a resync going on this mailbox?
while (node)
{
// it's possible we'll come across a node in the list that's been aborted but not cleaned up.
// this could happen during quit, if we're aborting multiple resync operations. Ignore
// nodes that are already aborted. jdboyd 03/22/02
if ((*node)->aborted == false)
{
state = HGetState((Handle)node);
LDRef(node);
if (SameTOC(toc, (*node)->toc))
{
HSetState((Handle)node, state);
// is this mailbox done with the network part of delivery?
if ((*node)->finished)
{
// then remove the delivery information form the list
DequeueDeliveryNode(node);
ZapDeliveryNode(&node);
}
else
{
// if not, mark the node as aborted. Let the thread take care of cleaning up.
AbortDeliveryNode(node);
// also, clean up marked for filtering messages
if ((*node)->filter) ResetFilterFlags(toc);
}
break;
}
else HSetState((Handle)node, state);
}
node = (*node)->next;
}
}
/************************************************************************
* NewDeliveryNode - set up a new delivery node
************************************************************************/
DeliveryNodeHandle NewDeliveryNode(TOCHandle tocH)
{
DeliveryNodeHandle node = NULL;
if (tocH)
{
node = NewZH(DeliveryNode);
if (node)
{
(*node)->toc = tocH;
(*node)->mailbox = TOCToMbox(tocH);
}
}
return (node);
}
/************************************************************************
* QueueDeliveryNode - queue a delivery node into our global list
************************************************************************/
void QueueDeliveryNode(DeliveryNodeHandle node)
{
if (node && ((*node)->mailbox || ((*node)->toc && (*(*node)->toc)->virtualTOC)))
{
if ((*node)->mailbox)
{
// ensure realistic tocRef
ASSERT((*(*node)->mailbox)->tocRef < 1000);
(*(*node)->mailbox)->tocRef++;
}
LL_Queue(gDeliveryQueue, node, (DeliveryNodeHandle));
}
else
{
// the caller has not properly set up the DeliverNodeHandle.
ASSERT(0);
}
}
/************************************************************************
* DequeueDeliveryNode - dequeue a delivery node from our global list
************************************************************************/
void DequeueDeliveryNode(DeliveryNodeHandle node)
{
if (node && ((*node)->mailbox || ((*node)->toc && (*(*node)->toc)->virtualTOC)) || ((*node)->aborted))
{
if ((*node)->mailbox)
{
// ensure realistic tocRef
ASSERT((*(*node)->mailbox)->tocRef < 1000);
if ((*(*node)->mailbox)->tocRef) (*(*node)->mailbox)->tocRef--;
}
}
else
{
// the DeliveryNodeHandle is no longer properly set up.
ASSERT(0);
}
LL_Remove(gDeliveryQueue, node, (DeliveryNodeHandle));
}
/************************************************************************
* AbortDeliveryNode - Given a DeilveryNode, mark it so it gets aborted
************************************************************************/
void AbortDeliveryNode(DeliveryNodeHandle node)
{
if (node)
{
(*node)->aborted = true; // set the aborted flag
(*node)->toc = nil; // forget about the mailbox, it may go away
}
}
/************************************************************************
* ZapDeliveryNode - Given a DeilveryNode, zap it and everyone it loves
************************************************************************/
void ZapDeliveryNode(DeliveryNodeHandle *node)
{
ZapHandle((**node)->ta);
ZapHandle((**node)->tu);
ZapHandle((**node)->tc);
if ((**node)->td != MSUM_DELETE_ALL) ZapHandle((**node)->td);
ZapHandle((**node)->results);
ZapHandle(*node);
}
/************************************************************************
* SameTOC - return true if two TOCs describe the same mailbox
************************************************************************/
Boolean SameTOC(TOCHandle toc1, TOCHandle toc2)
{
Boolean result = false;
FSSpec spec1;
FSSpec spec2;
if (toc1 && toc2)
{
spec1 = (*toc1)->mailbox.spec;
spec2 = (*toc2)->mailbox.spec;
result = SameSpec(&spec1, &spec2);
}
return (result);
}
/************************************************************************
* FindNodeByToc - given a toc, find the delivery node handle
************************************************************************/
DeliveryNodeHandle FindNodeByToc(TOCHandle toc)
{
DeliveryNodeHandle node = gDeliveryQueue;
SignedByte state;
while (node)
{
state = HGetState((Handle)node);
LDRef(node);
if (SameTOC(toc,(*node)->toc))
{
HSetState((Handle)node,state);
break;
}
HSetState((Handle)node,state);
node = (*node)->next;
}
return (node);
}
/************************************************************************
* UpdatableIMAPState - return true if this is a message state we
* can replace with the state of the message on the server.
*
* Updatable Summary states:
*
* - Messages marked as REPLIED should have their state updated.
* - Messages marked as READ should be updated to what the server says.
* - Messages marked as UNREAD should have their state updated.
*
* All other messages are storing a state that's not stored on the IMAP
* server. Leave them alone.
************************************************************************/
Boolean UpdatableIMAPState(StateEnum state)
{
Boolean result = false;
StateEnum index;
StateEnum updateableStates[] = {READ,UNREAD,REPLIED};
// If the message state is anything not updatable, ignore the state on the server.
for (index = 0; (!result) && (index < (sizeof (updateableStates) / sizeof (StateEnum))); index++)
if (updateableStates[index] == state)
result = true;
return (result);
}
/************************************************************************
* DoDownloadSingleMessage - given a summary, download the message,
* including, possibly, its attachments. This does the real work.
*
* Note: to fetch the message selectively, the server must return
* boundaries in the bodystructure fetch. If it doesn't, we're in the
* dark as to what the message looks like on the server, and better
* just fetch the whole message. JDB 2-8-99
*
* attachmentsToo == true, download message in one big chunk.
************************************************************************/
Boolean DoDownloadSingleMessage(IMAPStreamPtr imapStream, unsigned long uid, Boolean progress, Boolean attachmentsToo)
{
Boolean result = false;
IMAPBODY *body = nil;
PARAMETER *param;
long fPos, fPosNew;
Boolean html = false, rich = false;
Boolean showProgress = progress && !IMAPFilteringUnderway();
Boolean foundBoundary = false;
// Must have an open IMAP connection, and a mailbox must be SELECTed.
if (!IsSelected(imapStream->mailStream)) goto done;
// figure out where our file is at
if (GetMessDestFilePos(imapStream, &fPos) != noErr) goto done;
// get an idea of what this message looks like ...
if ((body=UIDFetchStructure(imapStream, uid))!=nil)
{
// iterate through the body's parameter, looking for BOUNDARY
param = body->parameter;
while (param && !foundBoundary)
{
if (param->attribute && strlen(param->attribute) && !striscmp(param->attribute,"BOUNDARY")) foundBoundary = true;
param = param->next;
}
}
// now fetch the message completely if the preference is set, or if no boundaries were found.
if (attachmentsToo || !foundBoundary || SpecialCaseDownload(body) || PrefIsSet(PREF_IMAP_FETCH_ATTACHMENTS_WITH_MESSAGE))
{
// POP mode - grab the whole thing right now.
if (imapStream->mailStream->showProgress = showProgress)
PrepareDownloadProgress(imapStream, GetRfc822Size(imapStream, uid));
result = UIDFetchMessage(imapStream, uid, true); // fetch the message, but don't mark it as \Seen
imapStream->mailStream->showProgress = false;
}
else
{
// get the message selectively
if (body)
{
// Fetch and save the header
if (result=UIDFetchHeader(imapStream, uid, true))
{
// Grab parts of the body now.
imapStream->mailStream->showProgress = showProgress;
if (body->type == TYPEMULTIPART)
{
result = DownloadMultipartBodyToSpoolFile(imapStream, uid, body, nil, false);
}
else
{
// not multipart. Just download the whole spiel.
result = DownloadSimpleBodyToSpoolFile(imapStream, uid, body, "1");
}
imapStream->mailStream->showProgress = false;
}
}
}
// terminate the current message
FSWriteP(imapStream->mailStream->refN,"\p\r");
// where's our file now?
if (GetMessDestFilePos(imapStream, &fPosNew) != noErr) result = false;
done:
// update the index of messages in this temporary file
if (result) UpdateIMAPTempFileIndex(imapStream, uid, fPos, fPosNew);
// Cleanup
if (body) FreeBodyStructure(body);
return (result);
}
/**********************************************************************
* UIDDownloadMessage - start a thread that goes and
* downloads message with UID uid into the mailbox described
* by TOCHandle tocH.
**********************************************************************/
OSErr UIDDownloadMessage(TOCHandle inToc, unsigned long uid, Boolean forceForeground, Boolean attachmentsToo)
{
Handle uidH = nil;
OSErr err = noErr;
uidH = NuHandle(sizeof(unsigned long));
if (uidH)
{
*((unsigned long *)(*uidH)) = uid;
err = UIDDownloadMessages(inToc, uidH, forceForeground, attachmentsToo);
}
else
{
WarnUser(err=MemError(),MEM_ERR);
}
return(err);
}
/**********************************************************************
* UIDDownloadMessages - start a thread that goes and downloads all
* messages specified in the uids handle
**********************************************************************/
OSErr UIDDownloadMessages(TOCHandle inToc, Handle uids, Boolean forceForeground, Boolean attachmentsToo)
{
IMAPTransferRec imapInfo;
OSErr err = noErr;
MailboxNodeHandle mailboxNode;
PersHandle oldPers = CurPers;
XferFlags flags;
// make sure we where given a tocH
if (!inToc) return (IMAPParamErr);
// we must be online
if (Offline && GoOnline()) return(OFFLINE);
// figure out who this mailbox belongs to
mailboxNode = TOCToMbox(inToc);
CurPers = TOCToPers(inToc);
if (mailboxNode && CurPers)
{
if (PrefIsSet(PREF_THREADING_OFF) || !ThreadsAvailable() || forceForeground)
{
// download the attachment
err = DoDownloadMessages(inToc, uids, attachmentsToo)?noErr:1;
}
else
{
// collect password now if we need it.
err = CheckIMAPSettingsForPers();
// if we were given a password, set up the thread
if (err==noErr)
{
Zero(flags);
Zero(imapInfo);
imapInfo.uids = uids;
imapInfo.destToc = inToc;
imapInfo.command = IMAPFetchingTask;
imapInfo.attachmentsToo = attachmentsToo;
err = SetupXferMailThread (false, false, true, false, flags, &imapInfo);
// and remove any old task errors
RemoveTaskErrors(IMAPFetchingTask,(*CurPers)->persId);
// forget the keychain password so it's not written to the settings file
if (KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN)) Zero((*CurPers)->password);
}
}
}
CurPers = oldPers;
return (err);
}
/************************************************************************
* DownloadSingleMessage - given a uid, download the message,
* including, possibly, its attachments.
************************************************************************/
Boolean DoDownloadMessages(TOCHandle tocH, Handle uids, Boolean attachmentsToo)
{
Boolean result = false;
IMAPStreamPtr imapStream;
PersHandle oldPers = CurPers;
PersHandle pers = nil;
MailboxNodeHandle mailboxNode = nil;
Str255 progressMessage, mName;
FSSpec spoolSpec;
short numUids;
long sumNum;
unsigned long newUIDValidity;
SignedByte tstate;
short totalUids;
Boolean fetchedAtLeastOne = false;
// must have a toc
if (!tocH) return (false);
// must have a list of uids to download
if (!uids || !*uids) return (false);
// must have at least one message to download
if ((numUids = GetHandleSize(uids)/sizeof(unsigned long))==0) return (false);
// see if this is indeed an IMAP mailbox
mailboxNode = TOCToMbox(tocH);
pers = TOCToPers(tocH);
if (mailboxNode && pers)
{
CurPers = pers;
// display some progress indicating which mailbox we're downloading to.
LockMailboxNodeHandle(mailboxNode);
PathToMailboxName ((*mailboxNode)->mailboxName, mName, (*mailboxNode)->delimiter);
UnlockMailboxNodeHandle(mailboxNode);
PROGRESS_START;
ComposeRString(progressMessage,IMAP_FETCHING_MESSAGE,mName);
PROGRESS_MESSAGE(kpTitle,progressMessage);
PROGRESS_MESSAGER(kpSubTitle,IMAP_LOGGING_IN);
// Create a new IMAP stream
imapStream = GetIMAPConnection(IMAPFetchingTask, CAN_PROGRESS);
if (imapStream)
{
// SELECT the mailbox this message resides in on the server
LockMailboxNodeHandle(mailboxNode);
if (IMAPOpenMailbox(imapStream, (*mailboxNode)->mailboxName,false))
{
PROGRESS_MESSAGER(kpSubTitle,LEFT_TO_TRANSFER);
// make sure the uidvalidty of the mailbox hasn't changed. If it has, then we shouldn't fetch the specified messages before doing a resync.
newUIDValidity = UIDValidity(imapStream);
if (newUIDValidity == (*mailboxNode)->uidValidity)
{
// iterate through the list of uids, and fetch each message.
totalUids = numUids;
for (numUids = 0; (numUids < totalUids) && !CommandPeriod; numUids++)
{
PROGRESS_BAR((100*numUids)/totalUids,totalUids - numUids,nil,nil,nil);
if (TOCFindMessByMID(((unsigned long *)(*uids))[numUids], tocH, &sumNum)==noErr)
{
// indicate which message we're downloading
tstate = HGetState((Handle)tocH);
LDRef(tocH);
PCopy(progressMessage,(*tocH)->sums[sumNum].from);
*progressMessage = MIN(*progressMessage,31);
PCatC(progressMessage,',');
PCatC(progressMessage,' ');
PSCat(progressMessage,(*tocH)->sums[sumNum].subj);
PROGRESS_MESSAGE(kpMessage,progressMessage);
BYTE_PROGRESS(nil,0,(*tocH)->sums[sumNum].length);
HSetState((Handle)tocH, tstate);
// go download the message
result = false;
// tell the stream where to put it.
imapStream->mailStream->refN = OpenMessDestFile(mailboxNode, ((unsigned long *)(*uids))[numUids], &spoolSpec);
// get the message
if (imapStream->mailStream->refN > 0)
{
result = DoDownloadSingleMessage(imapStream, ((unsigned long *)(*uids))[numUids],true,attachmentsToo);
// if we failed to get the message, it's probably because the mailbox has changed.
if (!result)
{
IMAPError(kIMAPFetch, kIMAPMailboxChangedErr, errIMAPMailboxChangedErr);
// Zap the spool file
FSpDelete(&spoolSpec);
}
else fetchedAtLeastOne = true;
//close the file
MyFSClose(imapStream->mailStream->refN);
imapStream->mailStream->refN = -1;
}
}
}
}
else
IMAPError(kIMAPFetch, kIMAPMailboxChangedErr, errIMAPMailboxChangedErr);
}
else // failed to open the destination mailbox.
IE(imapStream,kIMAPFetch, kIMAPSelectMailboxErr, errIMAPSelectMailbox);
UnlockMailboxNodeHandle(mailboxNode);
PROGRESS_MESSAGER(kpSubTitle,CLEANUP_CONNECTION);
// close down and clean up. This closes the destination file, but leaves the connection open
CleanupConnection(&imapStream);
// if we succeeded, then there's a message waiting now.
if (fetchedAtLeastOne)
{
(*tocH)->imapMessagesWaiting = 1;
TOCSetDirty(tocH,true);
}
}
PROGRESS_END;
CurPers = oldPers;
}
else
IMAPError(kIMAPFetch, kIMAPNotIMAPMailboxErr, errNotIMAPMailboxErr);
ZapHandle(uids);
return (result);
}
/************************************************************************
* OpenMessDestFile - create and open a temp file for a mailbox.
* We'll close the file when the imapStream is zapped.
************************************************************************/
short OpenMessDestFile(MailboxNodeHandle mailboxInfo, unsigned long uid, FSSpecPtr spoolSpec)
{
short ref = -1;
Str31 tempName;
OSErr err = noErr;
FSSpec spool;
long dirID;
Str255 ctext;
long creator;
// figure out the name of the temporary mailbox. Must be unique per message fetch operation.
LockMailboxNodeHandle(mailboxInfo);
IMAPTempFileName((*mailboxInfo)->uidValidity, uid, tempName);
UnlockMailboxNodeHandle(mailboxInfo);
// create (or open) the spool file in the spool folder.
if ((err=SubFolderSpec(SPOOL_FOLDER,&spool))==noErr)
{
// locate the spool file
dirID = SpecDirId(&spool);
err = FSMakeFSSpec(spool.vRefNum,spool.parID,tempName,spoolSpec);
if (err == fnfErr)
{
// not found in spool folder. Create the file.
GetPref(ctext,PREF_CREATOR);
if (*ctext!=4) GetRString(ctext,TEXT_CREATOR);
BMD(ctext+1,&creator,4);
err = HCreate(spoolSpec->vRefNum,spoolSpec->parID,spoolSpec->name,creator,'TEXT');
}
// open the file
if (err == noErr)
{
if ((err=AFSHOpen(spoolSpec->name,spoolSpec->vRefNum,spoolSpec->parID,&ref,fsRdWrPerm))==noErr)
{
// kill any existing resources
FSpKillRFork(spoolSpec);
// Start writing at the beginning of the file
if ((err=SetEOF(ref,0))==noErr)
err = SetFPos(ref, fsFromStart, 0);
}
}
}
if ((err != noErr) || (ref == -1))
{
WarnUser(NO_TEMP_FILE, err);
ref = -1;
}
return (ref);
}
/************************************************************************
* GetMessDestFilePos - get file position of destination file
************************************************************************/
OSErr GetMessDestFilePos(IMAPStreamPtr imapStream, long *pos)
{
OSErr err = GetFPos(imapStream->mailStream->refN, pos);
return (err);
}
/************************************************************************
* IMAPTempFileName - build the name of the temporary mailbox for
* the imap message fetch operation. The name will be
* <mailbox uidvalidity><uid of first message>.tmp
************************************************************************/
void IMAPTempFileName(UIDVALIDITY uv, unsigned long uid, Str31 fileName)
{
Str31 tempSuffix;
Str31 uvString;
Str31 uidString;
NumToString(uv,uvString);
NumToString(uid,uidString);
GetRString(tempSuffix,TEMP_SUFFIX);
ComposeRString(fileName, IMAP_TEMP_MAILBOX_FORMAT, uvString, uidString, tempSuffix);
}
/***************************************************************************
* UpdateIMAPTempFileIndex - we keep track of the offset and length of
* each message in a temp IMAP file in the 'IIND' resource.
***************************************************************************/
OSErr UpdateIMAPTempFileIndex(IMAPStreamPtr imapStream, unsigned long uid, long oldFilePosition, long newFilePosition)
{
OSErr err = noErr;
FSSpec tempSpec;
long count = 0;
long oldSize = 0;
short oldResFile = CurResFile();
Handle resource = 0;
short resRef = -1;
Boolean newRes = false;
// figure out where the temporary file is
if ((err=GetFileByRef(imapStream->mailStream->refN, &tempSpec))!=noErr) return (err);
// create a resource fork if we have to
if ((err=MakeResFile(tempSpec.name, tempSpec.vRefNum, tempSpec.parID, CREATOR, 'TEXT'))==noErr)
{
resRef = FSpOpenResFile(&tempSpec, fsRdWrPerm);
if ((resRef > 0) && (err=ResError())==noErr)
{
UseResFile(resRef);
// Get the existing index resource
resource = Get1Resource(INDEX_RES_TYPE,INDEX_RES_ID);
if (ResError()!=noErr)
{
ZapHandle(resource);
}
// add the latest uid, offset, and length to the index handle.
if (resource)
{
oldSize = GetHandleSize(resource);
SetHandleSize(resource, oldSize + sizeof(IndexStruct));
err = ResError();
}
else
{
resource = NuHandle(sizeof(IndexStruct));
err = ResError();
newRes = true;
}
if ((err==noErr) && resource)
{
// where does the new indexStruct end up?
count = (oldSize/sizeof(IndexStruct));
// add it to the resource handle.
((IndexStructPtr)(*resource))[count].uid = uid;
((IndexStructPtr)(*resource))[count].offset = oldFilePosition;
((IndexStructPtr)(*resource))[count].length = newFilePosition-oldFilePosition;
// put the resource back into the file
if (resource)
{
if (newRes) AddResource(resource,INDEX_RES_TYPE,INDEX_RES_ID,"");
else ChangedResource(resource);
if ((err=ResError())==noErr)
{
MyUpdateResFile(resRef);
err = ResError();
}
}
}
}
if (err != noErr)
{
// error saving to temporary file
WarnUser(NO_TEMP_FILE, err);
}
CloseResFile(resRef);
}
UseResFile(oldResFile);
return(err);
}
/************************************************************************
* IMAPMessagesWaiting - fill in spoolSpec with the oldest waiting
* message for the mailbox described by tocH. Return false if
* there's nothing to deliver.
************************************************************************/
Boolean IMAPMessagesWaiting(TOCHandle tocH, FSSpecPtr spoolSpec)
{
Boolean foundOne = false;
OSErr err = noErr;
MailboxNodeHandle mailboxInfo = nil;
CInfoPBRec hfi;
FSSpec spoolFolder;
Str255 name;
// don't do anything if we know there isn't anything in the spool folder.
if ((*tocH)->imapMessagesWaiting == 0) return (false);
// see if this is indeed an IMAP mailbox
mailboxInfo = TOCToMbox(tocH);
spoolSpec->name[0] = 0;
if (mailboxInfo != nil)
{
// Scan through Spool Folder, return first file that belongs to this box.
// Note, it just so happens that the "first" file will be the oldest file.
// Find the Spool Folder
if ((err=SubFolderSpec(SPOOL_FOLDER,&spoolFolder))==noErr)
{
// iterate through it
hfi.hFileInfo.ioNamePtr = name;
hfi.hFileInfo.ioFDirIndex = 0;
while (!foundOne && !DirIterate(spoolFolder.vRefNum,spoolFolder.parID,&hfi))
{
// only interested in files
if (!(hfi.hFileInfo.ioFlAttrib & ioDirMask))
{
// check the name of this file. Maybe it's a spool file for this mailbox.
if (IsSpooledMessageFile(name, mailboxInfo))
{
short ref = -1;
SimpleMakeFSSpec(spoolFolder.vRefNum,spoolFolder.parID,name,spoolSpec);
// is the file open? That is, is it still being written?
if ((err=AFSHOpen(spoolSpec->name,spoolSpec->vRefNum,spoolSpec->parID,&ref,fsRdWrPerm))==noErr)
{
MyFSClose(ref);
// has this temp file already been processed?
if (HasBeenProcessed(spoolSpec))
{
// delete it
if (!FSpDelete(spoolSpec))
hfi.hFileInfo.ioFDirIndex--;
continue;
}
else
{
// the file is ready to go. We found one.
foundOne = true;
break;
}
}
// else
// the file is opened for writing. The message is still being fetched, so leave it alone for now.
}
}
}
}
else // spool folder not found. No waiting messages.
{
(*tocH)->imapMessagesWaiting = 0;
}
}
// if there isn't anything in the spool folder, don't check again.
if (!foundOne && (spoolSpec->name[0]==0)) (*tocH)->imapMessagesWaiting = 0;
return (foundOne);
}
/************************************************************************
* IsSpooledMessageFile - return true if name is the name of a spool
* file waiting to be spooled into mailbox node.
************************************************************************/
Boolean IsSpooledMessageFile(Str255 name, MailboxNodeHandle node)
{
Boolean isSpool = false;
Str31 uvString;
Str31 tmp;
LockMailboxNodeHandle(node);
NumToString((*node)->uidValidity,uvString);
UnlockMailboxNodeHandle(node);
uvString[uvString[0]+1] = '.';
uvString[0] = uvString[0] + 1;
// name must begin with the UIDValidity of the mailbox, uvString.
if (BeginsWith(name,uvString))
{
// name must end with a .tmp suffix
GetRString(tmp,TEMP_SUFFIX);
if (EndsWith(name,tmp))
{
// then this is a valid spool file.
isSpool = true;
}
}
return (isSpool);
}
/************************************************************************
* DownloadSimpleBodyToSpoolFile - download simple body part to the
* mailbox file. If it's text, save it. Otherwise, break it out.
************************************************************************/
Boolean DownloadSimpleBodyToSpoolFile(IMAPStreamPtr imapStream, unsigned long uid, IMAPBODY *body, char *section)
{
Boolean result = false;
// must have uid, body, and a section
if (!uid || !body || !section) return (false);
// Make sure we're connected to an IMAP stream.
if (!imapStream || !IsSelected(imapStream->mailStream)) return (false);
// doesn't really matter what we're downloading. Stick it in the spool file. Someone else will knwo what to do with it.
// get the body part ...
switch (body->type)
{
case TYPETEXT:
// Get body text ...
result = AppendBodyTextToSpoolFile(imapStream, uid, section, body->size.bytes);
break;
case TYPEMESSAGE:
case TYPEMULTIPART:
return (false);
break;
// For all others, break out to file.
default:
result = AppendBodyTextToSpoolFile(imapStream, uid, section, body->size.bytes);
break;
}
return (result);
}
/************************************************************************
* AppendBodyTextToMbxFile - Download the contents of the body section
* as text and append it to the spool file
************************************************************************/
Boolean AppendBodyTextToSpoolFile(IMAPStreamPtr imapStream, unsigned long uid, char *section, long totalSize)
{
Boolean result = false;
// must have uid and a section
if (!uid || !section) return (false);
// must be connected to an IMAP server, and must have a mailbox selected ...
if (!imapStream || !IsSelected(imapStream->mailStream)) return (false);
PrepareDownloadProgress(imapStream, totalSize?totalSize:GetRfc822Size(imapStream,uid));
// actually go get the body text now. Try to fetch it in chunks.
if (totalSize) result = UIDFetchBodyTextInChunks(imapStream, uid, section, true, totalSize);
else result = UIDFetchBodyText(imapStream, uid, section, true);
return result;
}
/************************************************************************
* DownloadMultipartBodyToSpoolFile - Main multipart body dispatcher.
* Handles Multipart/Alternative, Multipart/Related specially.
************************************************************************/
Boolean DownloadMultipartBodyToSpoolFile(IMAPStreamPtr imapStream, unsigned long uid, IMAPBODY *parentBody, char *parentSection, Boolean doingRelated)
{
Str255 pSection, section;
Boolean result = false;
Boolean topLevel = true;
PART *part = nil;
IMAPBODY *body = nil;
Boolean doingMRelated = doingRelated, doingMAlternative = false, doingAppleDouble = false;
short partNum = 0;
Str255 pRelated, pAlternative, pAttachment, pAppledouble;
Str255 attachment;
Boolean wroteBoundary = false; // set to true once we know we need a closing boundary
// Make sure we have an imap stream and we've SELECTed a mailbox.
if (!imapStream || !IsSelected(imapStream->mailStream)) return (false);
// Must have a body.
if (!parentBody) return false;
// The body MUST have a subtype, otherwise treat as plain text.
if (!parentBody->subtype)
{
return (AppendBodyTextToSpoolFile(imapStream, uid, ((parentSection && *parentSection) ? parentSection : "1"), parentBody->size.bytes));
}
// Copy parent section string.
if (parentSection == nil) topLevel = true;
else if (*parentSection == nil) topLevel = true;
else topLevel = false;
// If the subtype is not multipart alternative or related, then we might hafta save an attachment stub
GetRString(pRelated,MIME_RELATED);
if (!pstrincmp(parentBody->subtype, pRelated, pRelated[0]))
doingMRelated = true;
GetRString(pAlternative,MIME_ALTERNATIVE);
if (!pstrincmp(parentBody->subtype, pAlternative, pAlternative[0]))
doingMAlternative = true;
// If the subtype is AppleDouble, then we need to skip a part.
GetRString(pAppledouble,MIME_APPLEDOUBLE);
if (!pstrincmp(parentBody->subtype, pAppledouble,pAppledouble[0]))
doingAppleDouble = true;
// Loop through all parts:
part = parentBody->nested.part;
while (part)
{
// Is this the first part??
if (topLevel)
{
NumToString(++partNum,pSection); // no dot
PtoCcpy(section, pSection);
}
else
{
// Copy parent section first. MUST have a non-NULL parent section if not top level!
ComposeString(pSection, "\p%s.%d", parentSection, ++partNum);
PtoCcpy(section, pSection);
}
body = &(part->body);
//
// Make a stub file ...
//
// ... if we're doing part of an apple double OR if we're looking at an attachment bigger than the max download size
// AND
// if this part is an attachment, and there are additional parameters (which, we hope, will contain the filename)
//
GetRString(pAttachment, ATTACH);
PtoCcpy(attachment,pAttachment);
// if this part is going into a stub file, don't let any of it end up in the spool file.
if (!doingMRelated && !doingMAlternative
&& (doingAppleDouble?!GonnaGetThisAppleDouble(parentBody):PrefMakeAttachmentStub(body->size.bytes))
&& ((body->disposition.type && !striscmp(attachment, body->disposition.type) || (!body->disposition.type && doingAppleDouble))
&& ((body->disposition.parameter) || (body->parameter && body->parameter->attribute))))
{
// if we're doing apple double, skip over the current part, 'cause we're looking at a resource fork.
if (doingAppleDouble && part->next)
{
// new section string
ComposeString(pSection, "\p%s.%d", parentSection, ++partNum);
PtoCcpy(section, pSection);
// new part
part = part->next;
// new body
body = &(part->body);
}
// save the attachment stub to the IMAP attachments folder
result = SaveAttachmentStub(imapStream, uid, section, body);
}
else
{
// write the boundary and content type if we're doing a related part, a non-pAppledouble part, or an appledouble that will end up being downloaded now.
if (pstrincmp(body->subtype, pAppledouble, pAppledouble[0]) || doingMRelated || GonnaGetThisAppleDouble(body))
{
IMAPWriteBoundary(imapStream, parentBody, uid, section, false);
wroteBoundary = true;
}
switch (body->type)
{
case TYPETEXT:
result = AppendBodyTextToSpoolFile(imapStream, uid, section, body->size.bytes);
break;
case TYPEMULTIPART:
result = DownloadMultipartBodyToSpoolFile(imapStream, uid, body, section, doingMRelated);
break;
default:
// otherwise, stick the attachment into the spool file.
result = AppendBodyTextToSpoolFile(imapStream, uid, section, body->size.bytes);
break;
} // switch
}
// Stop if we fail at any point ...
if (!result) break;
// Next part.
part = part->next;
}
// write the outer boundary if needed
if (wroteBoundary) IMAPWriteBoundary(imapStream, parentBody, uid, nil, true);
return (result);
}
/************************************************************************
* GonnaGetThisAppleDouble - Given a body, return true if the body
* describes an Appledouble part that we're going to be downloading
* immediately
************************************************************************/
Boolean GonnaGetThisAppleDouble(IMAPBODY *body)
{
Boolean getIt = false;
Str255 appledouble;
PART *part;
IMAPBODY *b = body;
unsigned long size = 0;
GetRString(appledouble,MIME_APPLEDOUBLE);
// the body's subtype must be appledouble
if (b && !pstrincmp(body->subtype, appledouble,appledouble[0]))
{
part = body->nested.part;
// add up the sizes of the parts
while (b && part)
{
b = &(part->body);
if (b) size += b->size.bytes;
part = part->next;
}
getIt = !PrefMakeAttachmentStub(size);
}
return (getIt);
}
/************************************************************************
* IMAPDeleteMessage - Delete a single message from an IMAP server.
************************************************************************/
Boolean IMAPDeleteMessage(TOCHandle tocH, unsigned long uid, Boolean nuke, Boolean expunge, Boolean undelete)
{
Handle uidH = nil;
Boolean result;
uidH = NuHandle(sizeof(unsigned long));
if (uidH)
{
*((unsigned long *)(*uidH)) = uid;
result = IMAPDeleteMessages(tocH, uidH, nuke, expunge, undelete, false);
}
else
{
WarnUser(MemError(),MEM_ERR);
}
return(result);
}
/************************************************************************
* IMAPDeleteMessages - Delete a series of messages from an IMAP server
************************************************************************/
Boolean IMAPDeleteMessages(TOCHandle tocH, Handle uids, Boolean nuke, Boolean expunge, Boolean undelete, Boolean forceForeground)
{
Boolean result = false;
XferFlags flags;
IMAPTransferRec imapInfo;
MailboxNodeHandle mailboxNode;
short numUids, sumNum, count;
OSErr err = noErr;
MailboxNodeHandle trash = NULL;
// must have a tocH and some messages to delete
if (!tocH || !uids) return (false);
PushPers(CurPers);
// how many messages are to be deleted?
numUids = GetHandleSize(uids)/sizeof(unsigned long);
// figure out who this mailbox belongs to
mailboxNode = TOCToMbox(tocH);
CurPers = TOCToPers(tocH);
// Fancy Trash Mode - transfer a copy of the message to the trash mailbox.
if (FancyTrashForThisPers(tocH))
{
// first, make sure we can find the trash
trash = GetIMAPTrashMailbox (CurPers, true, false);
// next, Close all messages to be deleted
for (count=0;count<numUids;count++)
{
for (sumNum=0;sumNum<(*tocH)->count;sumNum++)
{
if ((*tocH)->sums[sumNum].uidHash == ((unsigned long *)(*uids))[count])
{
if ((*tocH)->sums[sumNum].messH) CloseMyWindow(GetMyWindowWindowPtr((*(*tocH)->sums[sumNum].messH)->win));
break;
}
}
}
}
// are we deleting?
if (!undelete)
{
// are deleted messages hidden?
if (!DoesIMAPMailboxNeed(mailboxNode,kShowDeleted))
{
// then hide and queue deletions.
PopPers();
return (FastIMAPMessageDelete(tocH, uids, FancyTrashForThisPers(tocH)));
}
}
// else
// perform all other cases immediately. Like undelete.
// we must be online
if (Offline && GoOnline())
{
PopPers();
return(OFFLINE);
}
// if we're doing FTM,
if (FancyTrashForThisPers(tocH))
{
// make sure there's a trash mailbox somewhere
if (trash == nil)
{
PopPers();
return (false);
}
// delete in the trash mailbox means nuke
if (trash == mailboxNode) nuke = true;
}
if (forceForeground || PrefIsSet(PREF_THREADING_OFF) || !ThreadsAvailable())
{
result = DoDeleteMessage(tocH, uids, nuke, expunge, undelete);
if (result) UpdateIMAPMailbox(tocH);
}
else
{
// collect password now if we need it.
err = CheckIMAPSettingsForPers();
// if we were given a password, set up the thread and hide the summaries
if (err==noErr)
{
Zero(flags);
Zero(imapInfo);
imapInfo.delToc = tocH;
imapInfo.uids = uids;
imapInfo.nuke = nuke;
imapInfo.expunge = expunge;
imapInfo.command = undelete?IMAPUndeleteTask:IMAPDeleteTask;
if (SetupXferMailThread(false, false, true, false, flags, &imapInfo) == noErr)
result = true;
// and remove any old task errors
RemoveTaskErrors((undelete?IMAPUndeleteTask:IMAPDeleteTask),(*CurPers)->persId);
// forget the keychain password so it's not written to the settings file
if (KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN)) Zero((*CurPers)->password);
}
}
PopPers();
return (result);
}
/************************************************************************
* IMAPDeleteMessageDuringFiltering - Delete a single message from an
* IMAP mailbox. To be used during filtering.
*
* This routine is filter-friendly:
*
* - copies the message to the trash if FTM is on
* - marks the message as deleted
* - flags the mailbox as needing an expunge, if FTM is on
************************************************************************/
Boolean IMAPDeleteMessageDuringFiltering(TOCHandle tocH, PersHandle pers, unsigned long uid)
{
Boolean result = false;
short sumNum = UIDToSumNum(uid, tocH);
// must have a valid sumNum
if (sumNum < 0) return (false);
// must have an IMAP tocH
if (!tocH || !(*tocH)->imapTOC) return (false);
// must have a valid sumNum
if ((sumNum < 0) || (sumNum > (*tocH)->count)) return (false);
// must know the personality that's currently filtering
if (!pers) return (false);
// If FTM is off, just mark the message as deleted
if (!FancyTrashForThisPers(tocH))
{
result = IMAPMarkMessageAsDeleted(tocH, uid, false);
}
else
{
MailboxNodeHandle trashNode = nil;
TOCHandle trashToc = nil;
// locate the trash mailbox
trashNode = GetIMAPTrashMailbox(pers, false, false);
if (trashNode)
{
LockMailboxNodeHandle(trashNode);
trashToc = TOCBySpec(&(*trashNode)->mailboxSpec);
UnlockMailboxNodeHandle(trashNode);
if (trashToc)
{
// copy the message from this mailbox to the trash
if (IMAPTransferMessage(tocH,trashToc,uid,true,true) == noErr)
{
// mark it as deleted. We'll expunge when filtering is complete
result = IMAPMarkMessageAsDeleted(tocH, uid, false);
if (result) FlagForExpunge(tocH);
}
}
}
// else
// do nothing. The trash wasn't found
}
return (result);
}
/************************************************************************
* IMAPDeleteMessageFromSearchWindow - Delete all selected IMAP messages
* from the Search Window.
*
* This routine is search window-friendly. It connects to each IMAP
* mailbox once, and deletes all selected messages.
************************************************************************/
Boolean IMAPDeleteMessagesFromSearchWindow(TOCHandle tocH)
{
Boolean result = false;
TOCHandle realTocH = nil, curTocH = nil;
Handle uids = nil;
Handle selectedSearchMessages;
short searchSumNum, sumNum, realSum;
long count;
// Must have a tocH, and it must be a search window.
if (!tocH || !*tocH || !(*tocH)->virtualTOC) return (false);
// keep track of which mmessages we process
selectedSearchMessages = NuHandleClear(((*tocH)->count)*sizeof(Boolean));
if (selectedSearchMessages)
{
for (searchSumNum = 0; (searchSumNum < (*tocH)->count); searchSumNum++)
if ((*tocH)->sums[searchSumNum].selected) (*selectedSearchMessages)[searchSumNum] = true;
// go through all messages in the search toc
for (searchSumNum = 0; (searchSumNum < (*tocH)->count); searchSumNum++)
{
// if this message is selected ...
if ((*selectedSearchMessages)[searchSumNum])
{
if (!(realTocH = GetRealTOC(tocH,searchSumNum,&realSum)))
{
(*selectedSearchMessages)[searchSumNum] = false; // let's not try to process this one again
continue;
}
// process the mailbox this selected message belongs to
curTocH = realTocH;
count = 0;
if ((*curTocH)->imapTOC)
{
// count number of messages in the search window to be deleted from this mailbox
for (sumNum = 0; (sumNum < (*tocH)->count); sumNum++)
{
if ((*selectedSearchMessages)[sumNum])
{
if (!(realTocH = GetRealTOC(tocH,sumNum,&realSum)))
{
(*selectedSearchMessages)[sumNum] = false; // let's not try to process this one again
continue;
}
if (realTocH == curTocH) count++;
}
}
// delete them
uids = NuHandleClear(count*sizeof(unsigned long));
if (uids)
{
LDRef(curTocH);
for (sumNum = 0; (sumNum < (*tocH)->count); sumNum++)
{
if ((*selectedSearchMessages)[sumNum])
{
if (!(realTocH = GetRealTOC(tocH,sumNum,&realSum)))
{
(*selectedSearchMessages)[sumNum] = false; // let's not try to process this one again
continue;
}
if (realTocH == curTocH)
{
BMD(&((*curTocH)->sums[realSum].uidHash),&((unsigned long *)(*uids))[--count],sizeof(unsigned long));
(*selectedSearchMessages)[sumNum] = false;
// no matter if the message moved or not, mark it as processed so we don't try to delete it again.
(*tocH)->sums[sumNum].u.virtualMess.virtualMBIdx = -1;
InvalSum(tocH, sumNum);
}
}
}
UL(curTocH);
// and delete them.
result = IMAPDeleteMessages(curTocH, uids, false, false,false, false);
}
}
else
{
(*selectedSearchMessages)[searchSumNum] = false;
}
}
}
// Cleanup
ZapHandle(selectedSearchMessages);
}
return (result);
}
/************************************************************************
* IMAPTransferMessagesFromSearchWindow - transfer selected messages
* from the search window.
*
* This is no straightforward. Every message could come from a different
* mailbox.
************************************************************************/
OSErr IMAPTransferMessagesFromSearchWindow(TOCHandle fromTocH, TOCHandle toTocH, Boolean copy)
{
OSErr err = noErr;
short sumNum;
TOCHandle realTocH;
short realSum;
short count, i;
// structure to keep
typedef struct TransferInfoStruct TransferInfoStruct, *TransferInfoPtr, **TransferInfoHandle;
struct TransferInfoStruct
{
TOCHandle fromTocH;
Accumulator ids;
};
short numTransfers;
TransferInfoStruct transfer;
Accumulator transfers;
TransferInfoPtr curT;
Handle ids, idsToFetch;
short numIds;
long id;
long sum;
short numToFetch;
//
// First group the messages to be transferred into the transInfo struct.
//
numTransfers = 0;
err = AccuInit(&transfers);
for (sumNum = 0; (sumNum < (*fromTocH)->count) && (err == noErr); sumNum++)
{
if ((*fromTocH)->sums[sumNum].selected)
{
if (!(realTocH = GetRealTOC(fromTocH,sumNum,&realSum))) continue;
{
// only consider this message if we're going to or from an IMAP mailbox
if ((*realTocH)->imapTOC || (*toTocH)->imapTOC)
{
// see if this TOCH has already been added to the transfers
for (count = 0; count < numTransfers; count++)
{
curT = &((TransferInfoPtr)(*transfers.data))[count];
if (curT->fromTocH == realTocH) break;
}
if (!curT || (count >= numTransfers))
{
// it hasn't. Add a new one.
Zero(transfer);
transfer.fromTocH = realTocH;
err = AccuInit(&transfer.ids);
if (err == noErr)
{
err = AccuAddPtr(&transfers, &transfer, sizeof(TransferInfoStruct));
curT = &((TransferInfoPtr)(*transfers.data))[numTransfers];
numTransfers++;
}
}
// add the appropriate ID to the accumulator
if (curT && (err == noErr))
{
if ((*realTocH)->imapTOC)
{
// if we're transferring an IMAp message, we'll do it by uid
id = (*realTocH)->sums[realSum].uidHash;
err = AccuAddPtr(&curT->ids, &id, sizeof(long));
}
else
{
// this is a pop message. We'll do it by sum nmber
err = AccuAddPtr(&curT->ids, &realSum, sizeof(short));
}
if (!copy && (err == noErr))
{
// invaidate the summary in the search window
(*fromTocH)->sums[sumNum].u.virtualMess.virtualMBIdx = -1;
InvalSum(fromTocH, sumNum);
}
}
}
}
}
}
// trim the transfers
AccuTrim(&transfers);
// trim the id handles
for (count = 0; count < numTransfers; count++)
{
curT = &((TransferInfoPtr)(*transfers.data))[count];
AccuTrim(&curT->ids);
}
//
// Now actually go do the transfers
//
LDRef(transfers.data);
for (count = 0; count < numTransfers; count++)
{
curT = &((TransferInfoPtr)(*transfers.data))[count];
realTocH = curT->fromTocH;
ids = curT->ids.data;
curT->ids.data = nil;
// these messages are coming out of an IMAP mailbox
if ((*realTocH)->imapTOC)
{
if ((*toTocH)->imapTOC)
{
// and they're going to an IMAP maiblox
err = IMAPTransferMessages(realTocH, toTocH, ids, copy, false);
}
else
{
// transferring to a POP mailbox
// make a handle containing the messages to transfer
numIds = GetHandleSize(ids)/sizeof(long);
numToFetch = 0;
for (i=0;i<numIds;i++)
{
err = TOCFindMessByMID(((unsigned long *)(*ids))[i], realTocH, &sum);
if (err == noErr)
if (!(IMAPMessageBeingDownloaded(realTocH, sum) || IMAPMessageDownloaded(realTocH, sum))) numToFetch++;
}
if (err == noErr)
{
if (numToFetch) idsToFetch = NuHandleClear(numToFetch*sizeof(unsigned long));
if (idsToFetch || !numToFetch)
{
if (numToFetch)
{
short idNum = 0;
for (i=0;i<numIds;i++)
{
TOCFindMessByMID(((unsigned long *)(*ids))[i], realTocH, &sum);
if (!(IMAPMessageBeingDownloaded(realTocH, sum) || IMAPMessageDownloaded(realTocH, sum)))
BMD(&((*realTocH)->sums[sum].uidHash),&((unsigned long *)(*idsToFetch))[idNum++],sizeof(unsigned long));
}
// copy the messages
err = UIDDownloadMessages(realTocH, idsToFetch, true, true);
}
if (err == noErr)
{
// now move the messages to the POP mailbox
for (i = 0; (err == noErr) && (i < numIds); i++)
{
err = TOCFindMessByMID(((unsigned long *)(*ids))[i], realTocH, &sum);
if (err == noErr)
{
// wait for the downloaded message to show up
while (!IMAPMessageDownloaded(realTocH,sum) && !CommandPeriod)
{
CycleBalls();
UpdateIMAPMailbox(realTocH);
}
err = AppendMessage(realTocH,sum,toTocH,copy,false,false);
}
}
// delete the messages if they were successfully transferred
if (!copy && (err == noErr))
{
IMAPDeleteMessages(realTocH, ids, false, false,false, false);
}
else ZapHandle(ids);
}
}
else
err = MemError();
}
}
}
else
{
// these messages are coming out of a POP mailbox
if ((*toTocH)->imapTOC)
{
// and will be transferred to an IMAP mailbox.
err = IMAPTransferMessagesToServer(realTocH, toTocH, ids, copy, false);
}
}
}
UL(transfers.data);
//
// Clean up
//
for (count = 0; count < numTransfers; count++)
{
curT = &((TransferInfoPtr)(*transfers.data))[count];
AccuZap(curT->ids);
}
AccuZap(transfers);
return (err);
}
/************************************************************************
* IMAPMoveIMAPMessages - move IMAP messages around. This spawns threads.
************************************************************************/
OSErr IMAPMoveIMAPMessages(TOCHandle fromTocH, TOCHandle toTocH, Boolean copy)
{
OSErr err = noErr;
Handle uids = nil;
long c = CountSelectedMessages(fromTocH);
short sumNum;
Boolean IMAPtoIMAP = ((*toTocH)->imapTOC && (*fromTocH)->imapTOC);
Boolean POPtoIMAP = ((*toTocH)->imapTOC && !(*fromTocH)->imapTOC);
// if we're moving messages from a virtual mailbox, the work has already been done.
if ((*fromTocH)->virtualTOC) return (noErr);
// build a list of uids to be transferred
if (IMAPtoIMAP) uids = NuHandleClear(c*sizeof(unsigned long));
else if (POPtoIMAP) uids = NuHandleClear(c*sizeof(short));
if (uids)
{
// IMAP to IMAP. Do an IMAP transfer.
if (IMAPtoIMAP)
{
// build a list of uids to transfer
LDRef(fromTocH);
for (sumNum=0;sumNum<(*fromTocH)->count && c;sumNum++)
if ((*fromTocH)->sums[sumNum].selected)
BMD(&((*fromTocH)->sums[sumNum].uidHash),&((unsigned long *)(*uids))[--c],sizeof(unsigned long));
UL(fromTocH);
// and send them on their merry way ...
err = IMAPTransferMessages(fromTocH, toTocH, uids, copy, false);
}
// POP to IMAP
else if (POPtoIMAP)
{
// build a list of sumNums to transfer
LDRef(fromTocH);
for (sumNum=0;sumNum<(*fromTocH)->count && c;sumNum++)
if ((*fromTocH)->sums[sumNum].selected)
BMD(&sumNum,&((short *)(*uids))[--c],sizeof(short));
UL(fromTocH);
// and send them to the IMAP server ....
err = IMAPTransferMessagesToServer(fromTocH, toTocH, uids, copy, false);
}
}
else
{
WarnUser(MemError(), MEM_ERR);
}
return (err);
}
/************************************************************************
* IMAPMarkMessageAsDeleted - Mark a single message in an imap mailbox
* for deletion. Don't care about FTM.
* NOT THREAD SAFE.
************************************************************************/
Boolean IMAPMarkMessageAsDeleted(TOCHandle tocH, unsigned long uid, Boolean undelete)
{
Boolean result = false;
IMAPStreamPtr imapStream;
PersHandle oldPers = CurPers;
MailboxNodeHandle mailboxNode = nil;
Str255 uidStr;
// see if this is indeed an IMAP mailbox
mailboxNode = TOCToMbox(tocH);
CurPers = TOCToPers(tocH);
if (mailboxNode && CurPers)
{
// Create a new IMAP stream
if (imapStream = GetIMAPConnection(IMAPDeleteTask, CAN_PROGRESS))
{
// SELECT the mailbox this message resides in on the server
LockMailboxNodeHandle(mailboxNode);
if (IMAPOpenMailbox(imapStream, (*mailboxNode)->mailboxName,false))
{
sprintf (uidStr,"%lu",uid);
result = undelete ? UIDUnDeleteMessages(imapStream, uidStr) : UIDDeleteMessages(imapStream, uidStr, false);
if (result)
{
short sumNum;
if ((sumNum = UIDToSumNum(uid, tocH)) >= 0)
{
if (undelete) MarkSumAsDeleted(tocH, sumNum, false);
else
{
MarkSumAsDeleted(tocH, sumNum, true);
// Close the message as well ...
if ((*tocH)->sums[sumNum].messH) CloseMyWindow(GetMyWindowWindowPtr((*(*tocH)->sums[sumNum].messH)->win));
}
// if fancy trash mode is off, then redraw the summary.
if (PrefIsSet(PREF_IMAP_NO_FANCY_TRASH)) InvalSum(tocH,sumNum);
}
}
}
UnlockMailboxNodeHandle(mailboxNode);
CleanupConnection(&imapStream);
}
}
CurPers = oldPers;
return (result);
}
/************************************************************************
* DoDeleteMessage - Mark a single message in an imap mailbox for
* deletion. Transfer it to the Trash mailbox if !nuke, and expunge.
************************************************************************/
Boolean DoDeleteMessage(TOCHandle tocH, Handle uids, Boolean nuke, Boolean expunge, Boolean undelete)
{
Boolean result = false;
IMAPStreamPtr imapStream;
PersHandle oldPers = CurPers;
MailboxNodeHandle mailboxNode = nil;
Str255 progressMessage, mName;
unsigned long numUids = GetHandleSize(uids)/sizeof(unsigned long);
unsigned long count = numUids;
MailboxNodeHandle trashNode = nil;
// Make sure we have something to delete ...
if (!tocH || !uids) return (false);
// see if this is indeed an IMAP mailbox
mailboxNode = TOCToMbox(tocH);
CurPers = TOCToPers(tocH);
if (mailboxNode && CurPers)
{
// Make sure we have a trash mailbox
if (FancyTrashForThisPers(tocH) && ((trashNode=GetIMAPTrashMailbox (CurPers, false, false))==nil))
{
CurPers = oldPers;
ZapHandle(uids);
return (false);
}
// display some progress indicating which mailbox we're downloading to.
LockMailboxNodeHandle(mailboxNode);
PathToMailboxName ((*mailboxNode)->mailboxName, mName, (*mailboxNode)->delimiter);
UnlockMailboxNodeHandle(mailboxNode);
if (undelete) ComposeRString(progressMessage,IMAP_UNDELETING_MESSAGES,mName);
else ComposeRString(progressMessage,IMAP_DELETING_MESSAGES,mName);
PROGRESS_START;
PROGRESS_MESSAGE(kpTitle,progressMessage);
// Create a new IMAP stream
if (imapStream = GetIMAPConnection(IMAPDeleteTask, CAN_PROGRESS))
{
// SELECT the mailbox this message resides in on the server
PROGRESS_MESSAGER(kpSubTitle,IMAP_LOGGING_IN);
LockMailboxNodeHandle(mailboxNode);
if (IMAPOpenMailbox(imapStream, (*mailboxNode)->mailboxName, false))
{
// if this stream is read only, don't even try to delete messages.
if (IsReadOnly(imapStream->mailStream))
{
IMAPError(undelete ? kIMAPUndelete : kIMAPDelete, kIMAPMailboxReadOnly, errIMAPReadOnlyStreamErr);
}
else
{
// FTM - copy messages to trash mailbox
if (FancyTrashForThisPers(tocH))
{
// copy all the messages to the trash mailbox, marking them as deleted, and expunging
if (!IsIMAPTrashMailbox(mailboxNode) && !nuke)
{
// now copy the messages to the trash
result = CopyMessages(imapStream, trashNode, uids, false, false);
// do an FTMExpunge to clean out the mailbox of deleted messages, UNLESS we're in the middle of filtering.
if (result && !IMAPFilteringUnderway()) FTMExpunge(imapStream, tocH);
}
// if nuking, mark all messages for deletion, and expunge
if (IsIMAPTrashMailbox(mailboxNode) || nuke)
{
// mark the message for deletion.
result = DoUIDMarkAsDeleted(imapStream, uids, undelete);
if (result) FTMExpunge(imapStream, tocH);
}
// update the summaries on screen if all went well ...
if (result) UpdateLocalSummaries(tocH, uids, true, true, false);
}
else
{
// go through the lists of sums, marking them each as deleted or undeleted, whichever we were told to do
result = DoUIDMarkAsDeleted(imapStream, uids, undelete);
// update the summary on screen if all went well ...
if (result) UpdateLocalSummaries(tocH, uids, undelete, false, false);
}
}
}
else // failed to open mailbox
IE(imapStream, undelete ? kIMAPUndelete : kIMAPDelete, kIMAPSelectMailboxErr, errIMAPSelectMailbox);
UnlockMailboxNodeHandle(mailboxNode);
// close down and clean up
PROGRESS_MESSAGER(kpSubTitle,CLEANUP_CONNECTION);
CleanupConnection(&imapStream);
PROGRESS_END;
}
CurPers = oldPers;
}
else //this wasn't an IMAP mailbox.
IMAPError(undelete ? kIMAPUndelete : kIMAPDelete, kIMAPNotIMAPMailboxErr, errNotIMAPMailboxErr);
// if we succeeded, and we're in FTM, and the trash mailbox is open, resync it why don't we.
if (FancyTrashForThisPers(tocH) && result && trashNode && !nuke)
{
TOCHandle trashToc = nil;
LockMailboxNodeHandle(trashNode);
if (tocH=FindTOC(&((*trashNode)->mailboxSpec)))
{
if ((*tocH)->win && IsWindowVisible(GetMyWindowWindowPtr((*tocH)->win))) FetchNewMessages(tocH, true, false, false, false);
}
UnlockMailboxNodeHandle(trashNode);
}
// kill the list of uids that we were supposed to delete
ZapHandle(uids);
return (result);
}
/************************************************************************
* DoUIDMarkAsDeleted - Delete a bunch of messages quickly, if allowed.
************************************************************************/
Boolean DoUIDMarkAsDeleted(IMAPStreamPtr stream, Handle uids, Boolean undelete)
{
short i, numMessages = GetHandleSize(uids)/sizeof(long);
Str255 progressMessage, uidStr;
long ticks, lastProgress = 0;
long uid;
Boolean result = true;
// Sort the list of messages to be copied for maximum performance
if (!PrefIsSet(PREF_IMAP_DONT_USE_UID_RANGE)) SortUIDListHandle(uids);
for (i=0; i<numMessages && result; i++)
{
// display some progress every second
if ((((ticks=TickCount())-lastProgress) > 60))
{
ComposeRString(progressMessage,IMAP_MARKING_MESSAGES, i+1, numMessages);
PROGRESS_MESSAGE(kpSubTitle,progressMessage);
lastProgress = ticks;
}
// build the range string for the command ...
if (PrefIsSet(PREF_IMAP_DONT_USE_UID_RANGE))
{
uid = ((unsigned long *)(*uids))[i];
sprintf (uidStr,"%lu",uid);
}
else
i = GenerateUIDString(uids, i, &uidStr);
// delete this (bunch of) message(s) ...
result = undelete ? UIDUnDeleteMessages(stream, uidStr) : UIDDeleteMessages(stream, uidStr, false);
// if we failed to delete messages from the server it's most likely that they've
// been removed already by another client. Simply cotninue as if nothing has
// gone wrong and the'll be removed them from the cache.
if (!undelete && !result)
result = true;
// display an error if we failed.
if (!result) IMAPError(kIMAPTransfer, kIMAPUndeleteMessage, errIMAPUndeleteMessage);
}
return (result);
}
/************************************************************************
* IMAPTransfer - Transfer a single message from one mailbox to
* another, where one or more of the mailboxes are IMAP mailboxes.
************************************************************************/
OSErr IMAPTransferMessage(TOCHandle fromTocH, TOCHandle toTocH, unsigned long uid, Boolean copy, Boolean forceForeground)
{
OSErr err = noErr;
Handle uidH = nil;
uidH = NuHandle(sizeof(unsigned long));
if (uidH)
{
*((unsigned long *)(*uidH)) = uid;
err = IMAPTransferMessages(fromTocH, toTocH, uidH, copy, forceForeground);
// don't zap the handle. Someone else will.
}
else
{
WarnUser(MemError(),MEM_ERR);
}
return (err);
}
/************************************************************************
* IMAPTransferMessages - Transfer a series of messages from one mailbox
* to another, where the source and/or destination is an IMAP mailbox.
************************************************************************/
OSErr IMAPTransferMessages(TOCHandle fromTocH, TOCHandle toTocH, Handle uids, Boolean copy, Boolean forceForeground)
{
OSErr err = noErr;
XferFlags flags;
IMAPTransferRec imapInfo;
PersHandle oldPers = CurPers;
PersHandle fromPers=nil, toPers=nil;
MailboxNodeHandle fromBox=nil, toBox=nil;
short sumNum, numUids, count;
// must have a source toc
if (fromTocH == toTocH) return (false);
// must have a destination toc
if (!fromTocH || !toTocH || !uids) return (false);
// we must be online, or be in the middle of filtering ...
if (IMAPFilteringWarnOffline() && Offline && GoOnline()) return(OFFLINE);
// Problem: once a message has been transferred to a new IMAP server, there's no
// way to keep track of it. It's assigned a new UID, and IMAP servers do not
// report this new UID. So, close all messages before transferring them.
if (!copy)
{
numUids = GetHandleSize(uids)/sizeof(unsigned long);
for (count=0;count<numUids;count++)
{
for (sumNum=0;sumNum<(*fromTocH)->count;sumNum++)
{
if ((*fromTocH)->sums[sumNum].uidHash == ((unsigned long *)(*uids))[count])
{
if ((*fromTocH)->sums[sumNum].messH) CloseMyWindow(GetMyWindowWindowPtr((*(*fromTocH)->sums[sumNum].messH)->win));
break;
}
}
}
}
if (PrefIsSet(PREF_THREADING_OFF) || !ThreadsAvailable() || forceForeground)
{
err = DoTransferMessages(fromTocH, toTocH, uids, copy);
if (err==noErr) UpdateIMAPMailbox(fromTocH);
}
else
{
// figure out where the source mailbox is
fromBox = TOCToMbox(fromTocH);
fromPers = TOCToPers(fromTocH);
// if we're doing FTM, and we're not copying ...
if (!copy && FancyTrashForThisPers(fromTocH))
{
MailboxNodeHandle trash = nil;
// and create the trash mailbox, or make the user select one.
trash = GetIMAPTrashMailbox(fromPers, true, false);
// make sure there's a trash mailbox somewhere
if (trash == nil) return (errIMAPNoTrash);
}
// get the password if we need it
if ((CurPers=fromPers) && PrefIsSet(PREF_IS_IMAP))
err = CheckIMAPSettingsForPers();
// figure out where the source mailbox is
toBox = TOCToMbox(toTocH);
toPers = TOCToPers(toTocH);
// get the password if we need it
if ((CurPers=toPers) && PrefIsSet(PREF_IS_IMAP))
err = CheckIMAPSettingsForPers();
// if the user didn't cancel, then set up the thread
if (err==noErr)
{
Zero(flags);
Zero(imapInfo);
imapInfo.destToc = toTocH;
imapInfo.sourceToc = fromTocH;
imapInfo.uids = uids;
imapInfo.copy = copy;
imapInfo.command = IMAPTransferTask;
err = SetupXferMailThread(false, false, true, false, flags, &imapInfo);
// and remove any old task errors
RemoveTaskErrors(IMAPTransferTask,(*fromPers)->persId);
RemoveTaskErrors(IMAPTransferTask,(*toPers)->persId);
}
CurPers = oldPers;
// forget the keychain password so it's not written to the settings file
if (KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN))
{
Zero((*fromPers)->password);
Zero((*toPers)->password);
}
}
return (err);
}
/************************************************************************
* DoTransferMessages - Transfer a message from a mailbox to another.
************************************************************************/
OSErr DoTransferMessages(TOCHandle fromTocH, TOCHandle toTocH, Handle uids, Boolean copy)
{
OSErr err = noErr;
PersHandle oldPers = CurPers;
PersHandle fromPers = nil, toPers = nil;
MailboxNodeHandle fromBox = nil, toBox = nil;
Boolean fromIsIMAP = false, toIsIMAP = false;
IMAPStreamPtr fromStream = nil, toStream = nil;
Boolean bNoResync = false;
//
// first figure out what sort of transfer this is
//
// figure out where the source mailbox is
fromBox = TOCToMbox(fromTocH);
fromPers = TOCToPers(fromTocH);
if ((CurPers=fromPers) && PrefIsSet(PREF_IS_IMAP)) fromIsIMAP = true;
// figure out where the destination mailbox is
toBox = TOCToMbox(toTocH);
toPers = TOCToPers(toTocH);
if ((CurPers=toPers) && PrefIsSet(PREF_IS_IMAP)) toIsIMAP = true;
//
// now do the transfer
//
// if we allocated our streams and found that at least one personality is an IMAP personality ...
if ((err == noErr) && (fromIsIMAP || toIsIMAP))
{
// be progressive
PROGRESS_START;
if (copy)
{
PROGRESS_MESSAGER(kpTitle,IMAP_COPY_MESSAGES);
}
else
{
PROGRESS_MESSAGER(kpTitle,IMAP_TRANSFER_MESSAGES);
}
// allocate the stream we'll need to talk to this mailbox
CurPers = fromPers;
if (fromIsIMAP) fromStream = GetIMAPConnection(IMAPTransferTask, CAN_PROGRESS);
// allocate the stream we'll need to talk to this mailbox, if it belongs to a different personality
CurPers = toPers;
if (toIsIMAP && (fromPers!=toPers)) toStream = GetIMAPConnection(IMAPAppendTask, CAN_PROGRESS);
// from and to are IMAP servers ...
if (fromIsIMAP && toIsIMAP)
{
// they're different personalities ...
if (fromPers != toPers) err = TransferMessageBetweenServers(fromPers, fromStream, fromBox, fromTocH, toPers, toStream, toBox, uids, copy);
// they're the same server
else
{
err = TransferMessageOnServer(fromPers, fromStream, fromBox, fromTocH, toBox, uids, copy);
// if UIDPLUS is available, there's no need to resync the destination mailbox when we're done.
bNoResync = !PrefIsSet(PREF_NO_USE_UIDPLUS) && JunkPrefHasIMAPSupport();
}
// update the mailboxes on screen
if (err == noErr)
{
// add new summaries to destination mailbox, and remove or mark the originals
if (!copy) UpdateLocalSummaries(fromTocH, uids, false, FancyTrashForThisPers(fromTocH),FancyTrashForThisPers(fromTocH));
}
}
PROGRESS_END;
}
//else
// neither the to nor the from is an IMAP personality. Something went wrong with one of the tocs. Do nothing.
//Cleanup
CleanupConnection(&fromStream);
CleanupConnection(&toStream);
// No matter what the outcome is, resync the destination mailbox to reflect any changes we may have made
if (!bNoResync)
{
if (IMAPFilteringUnderway()) SetIMAPMailboxNeeds(toBox, kNeedsResync, true);
else
{
Boolean oldCommandPeriod = CommandPeriod;
// resync right now if we're not in the process of filtering.
CommandPeriod = false;
if ((*toTocH)->win && IsWindowVisible(GetMyWindowWindowPtr((*toTocH)->win))) FetchNewMessages(toTocH, true, true, false, false);
CommandPeriod = CommandPeriod | oldCommandPeriod;
}
}
// did we change an IMAP mailbox?
if (toIsIMAP && toBox)
IMAPMailboxChanged(toBox);
CurPers = oldPers;
ZapHandle(uids);
return (err);
}
/************************************************************************
* TransferMessageBetweenServers - move a message from one mailbox
* on one server to another mailbox on another server
************************************************************************/
OSErr TransferMessageBetweenServers(PersHandle fromPers, IMAPStreamPtr fromStream, MailboxNodeHandle fromBox, TOCHandle fromTocH, PersHandle toPers, IMAPStreamPtr toStream, MailboxNodeHandle toBox, Handle uids, Boolean copy)
{
OSErr err = noErr;
Boolean result;
STRING ms;
short numSums = GetHandleSize(uids)/sizeof(unsigned long);
short count;
long totalSize = 0;
long seconds = 0;
STRINGDataStruct msData;
PersHandle oldPers = CurPers;
char *flags = nil;
UIDNodeHandle uidNode = nil;
Str255 seq;
unsigned long uid = 0;
// must have a source server and mailbox
if (!fromStream || !fromBox) return (IMAPParamErr);
// must have a destination server and mailbox
if (!toStream || !toBox) return (IMAPParamErr);
// must have a list of uids to transfer
if (!uids || (numSums==0)) return (IMAPParamErr);
// open and select the remote source mailbox
CurPers = fromPers;
PROGRESS_MESSAGER(kpSubTitle,IMAP_LOGGING_IN);
LockMailboxNodeHandle(fromBox);
if ((result=IMAPOpenMailbox(fromStream, (*fromBox)->mailboxName, false))==true)
{
// open and select the destination mailbox
CurPers = toPers;
LockMailboxNodeHandle(toBox);
if ((result=IMAPOpenMailbox(toStream, (*toBox)->mailboxName, false))==true)
{
// set up the STRINGDataStruct we'll use to read the source message
Zero(msData);
msData.buffer = NuPtr(GetRLong(IMAP_TRANSFER_BUFFER_SIZE) + 4);
if (msData.buffer)
{
msData.bufferSize = GetRLong(IMAP_TRANSFER_BUFFER_SIZE);
msData.imapStream = fromStream;
// iterate through the list of messages to be transferred
for (count = 0; (count < numSums) && result; count++)
{
uid = ((unsigned long *)(*uids))[count];
PROGRESS_MESSAGER(kpSubTitle,LEFT_TO_TRANSFER);
PROGRESS_BAR((100*count)/numSums,numSums - count,nil,nil,nil);
//set up the STRINGDataStruct for the current transfer
msData.uid = uid;
msData.bytesRead = 0;
// Get the total size of the message to be transferred
if ((totalSize=GetRfc822Size(fromStream, uid)) > 0)
{
// Initialize the STRING.
Zero(ms);
ms.dtb = &StreamAppendDriver;
ms.data = (void *)&msData;
(ms.dtb)->init(&ms, nil, totalSize);
// did we successfully start the read?
if ((result = msData.result) == true)
{
// the destination message will retain the same flags
sprintf (seq,"%lu",uid);
if (FetchFlags(fromStream, seq, &uidNode))
{
if (uidNode)
{
if ((*uidNode)->uid == uid)
{
FlagsString(&flags, (*uidNode)->l_seen, (*uidNode)->l_deleted, (*uidNode)->l_flagged, (*uidNode)->l_answered, (*uidNode)->l_draft, (*uidNode)->l_recent, (*uidNode)->l_sent);
}
ZapHandle(uidNode);
}
}
// now actually go do the append
result = IMAPAppendMessage(toStream, flags, seconds, &ms);
// display an error if the copy failed.
if (!result) IMAPError(kIMAPTransfer, kIMAPCopyErr, errIMAPCopyFailed);
// delete the old message from the source mailbox
if (result && !copy)
{
Str255 uidStr;
sprintf (uidStr,"%lu",uid);
result = UIDDeleteMessages(fromStream, uidStr, false);
}
}
else
IMAPError(kIMAPTransfer, kIMAPPartialFetchErr, errIMAPCopyFailed);
}
}
// get rid of the flags string
if (flags) ZapPtr(flags);
// get rid of the transfer buffer
ZapPtr(msData.buffer);
}
else
{
// failed to allocate buffer
WarnUser(MEM_ERR, err=MemError());
}
}
else // failed to open destination. Display some error message.
IE(toStream, kIMAPTransfer, kIMAPSelectMailboxErr, errIMAPSelectMailbox);
UnlockMailboxNodeHandle(toBox);
}
else // failed to open source mailbox
IE(fromStream, kIMAPTransfer, kIMAPSelectMailboxErr, errIMAPSelectMailbox);
// if FTM is on, then take care of resident deleted messages and expunge.
CurPers = fromPers;
if (!copy && result && FancyTrashForThisPers(fromTocH)) FTMExpunge(fromStream, fromTocH);
UnlockMailboxNodeHandle(fromBox);
CurPers = oldPers;
return(err==noErr?(result?noErr:1):err);
}
/************************************************************************
* TransferMessageOnServer - move a message from one mailbox
* to another on an IMAP server
************************************************************************/
OSErr TransferMessageOnServer(PersHandle fromPers, IMAPStreamPtr fromStream, MailboxNodeHandle fromBox, TOCHandle fromTocH, MailboxNodeHandle toBox, Handle uids, Boolean copy)
{
Boolean result = false;
PersHandle oldPers = CurPers;
// must have a destination and source mailbox specified.
if (!fromBox || !toBox) return (IMAPParamErr);
//must have a list of messages to transfer
if (!uids || !*uids) return (IMAPParamErr);
// must have a stream to talk to the IMAP server through
if (!fromStream) return (IMAPParamErr);
//SELECT the mailbox to transfer from
CurPers = fromPers;
LockMailboxNodeHandle(fromBox);
// copy the messages from the source to the destination
PROGRESS_MESSAGER(kpSubTitle,IMAP_LOGGING_IN);
if ((result=IMAPOpenMailbox(fromStream, (*fromBox)->mailboxName, false))==true)
{
result = CopyMessages(fromStream, toBox, uids, copy, false);
// if FTM is on, then take care of resident deleted messages and expunge.
if (result && !copy && FancyTrashForThisPers(fromTocH)) FTMExpunge(fromStream, fromTocH);
}
else IE(fromStream, kIMAPTransfer, kIMAPSelectMailboxErr, errIMAPSelectMailbox);
UnlockMailboxNodeHandle(fromBox);
//Cleanup
CurPers = oldPers;
return (result?noErr:1);
}
/************************************************************************
* IMAPTransferMessagesToServer - Transfer POP messages to an IMAP mbox.
************************************************************************/
OSErr IMAPTransferMessageToServer(TOCHandle tocH, TOCHandle toTocH, short sumNum, Boolean copy, Boolean forceForeground)
{
Handle sumsH = nil;
OSErr err = noErr;
sumsH = NuHandle(sizeof(short));
if (sumsH)
{
*((short *)(*sumsH)) = sumNum;
err = IMAPTransferMessagesToServer(tocH, toTocH, sumsH, copy, forceForeground);
}
else
{
WarnUser(err=MemError(),MEM_ERR);
}
return(err);
}
/************************************************************************
* IMAPTransferMessagesToServer - Transfer POP messages to an IMAP mbox.
************************************************************************/
OSErr IMAPTransferMessagesToServer(TOCHandle fromTocH, TOCHandle toTocH, Handle sumNums, Boolean copy, Boolean forceForeground)
{
OSErr err = noErr;
XferFlags flags;
IMAPTransferRec imapInfo;
PersHandle oldPers = CurPers;
PersHandle toPers=nil;
MailboxNodeHandle fromBox=nil, toBox=nil;
short sumNum, numSums, count, i;
IMAPAppendHandle spoolData = nil;
Boolean progress;
Str255 progressMessage;
// must have a source toc
if (fromTocH == toTocH) return (-1);
// must have a destination toc
if (!fromTocH || !toTocH || !sumNums) return (-1);
// we must be online, or be in the middle of filtering ...
if (IMAPFilteringWarnOffline() && Offline && GoOnline()) return(OFFLINE);
// we must have been told to transfer at least one message
numSums = GetHandleSize(sumNums)/sizeof(short);
if (numSums <= 0) return (-1);
//
// first, figure out where this message is going
//
toBox = TOCToMbox(toTocH);
toPers = TOCToPers(toTocH);
if (!toPers || !toBox) return (-1);
//
// encode all of the messages to be transferred to the server
//
// We'll need to remember each spool file:
spoolData = NuHandleClear(numSums * sizeof(IMAPAppendStruct));
if (((err=MemError())!=noErr) || !spoolData)
return (err);
// Keep the tocH around. We'll let it go below if there's an error setting up,
// or from DoTransferMessagesToServer when it's called below or from a thread.
IMAPTocHBusy(toTocH, true);
// Display some proress if this might take a while ...
progress = (numSums > 5);
if (progress)
{
PROGRESS_START;
PROGRESS_MESSAGER(kpTitle,IMAP_PREPARE_MESSAGES);
}
// encode each message.
for (count=0;(count<numSums) && (err==noErr);count++)
{
TOCHandle realTocH;
short realSumNum;
if (progress)
{
ComposeRString(progressMessage,IMAP_PREPARE_FMT, count + 1, numSums);
PROGRESS_MESSAGE(kpSubTitle,progressMessage);
}
if (!(realTocH=GetRealTOC(fromTocH,((short *)(*sumNums))[count],&realSumNum))) err = 1;
else
{
LDRef(spoolData);
err = SpoolOnePopMessage(realTocH, realSumNum, &((*spoolData)[count]));
UL(spoolData);
}
}
// if an error occurred, go kill all the spool specs
if (err != noErr)
{
for (i=0;(i<count);i++)
FSpDelete(&((*spoolData)[i]).spoolSpec);
ZapHandle(spoolData);
IMAPTocHBusy(toTocH, false);
return (err);
}
if (progress) PROGRESS_END;
//
// if we're not copying the messages, close them all. We'll delete them once they've been sent.
//
if (!copy)
{
for (count=0;count<numSums;count++)
{
sumNum = ((short *)(*sumNums))[count];
if ((*fromTocH)->sums[sumNum].messH) CloseMyWindow(GetMyWindowWindowPtr((*(*fromTocH)->sums[sumNum].messH)->win));
}
}
//
// Now upload them to the server. This can be done from a thread.
//
if (PrefIsSet(PREF_THREADING_OFF) || !ThreadsAvailable() || forceForeground)
{
err = DoTransferMessagesToServer(toTocH, spoolData, copy, true);
}
else
{
// get the password for the destination mailbox, if we need it.
if ((CurPers=toPers) && PrefIsSet(PREF_IS_IMAP))
err = CheckIMAPSettingsForPers();
// set up the thread
if (err==noErr)
{
Zero(flags);
Zero(imapInfo);
imapInfo.destToc = toTocH;
imapInfo.appendData = spoolData;
imapInfo.copy = copy;
imapInfo.command = IMAPUploadTask;
err = SetupXferMailThread(false, false, true, false, flags, &imapInfo);
// and remove any old task errors
RemoveTaskErrors(IMAPUploadTask,(*toPers)->persId);
}
else
{
// if we couldn't set up the transfer, we're done with the tocH.
IMAPTocHBusy(toTocH, false);
}
CurPers = oldPers;
// forget the keychain password so it's not written to the settings file
if (KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN))
{
Zero((*toPers)->password);
}
}
ZapHandle(sumNums);
return (err);
}
/************************************************************************
* SpoolOnePopMessage - spool a message to transfer it to an IMAP server
************************************************************************/
OSErr SpoolOnePopMessage(TOCHandle fromTocH, short fromSumNum, IMAPAppendPtr spoolData)
{
OSErr err = noErr;
MyWindowPtr messWin = nil;
WindowPtr messWinWP;
MessHandle messH = nil;
Boolean openedMessage = false;
FSSpecPtr spoolSpec = &(spoolData->spoolSpec);
// get the message from the toc and sumnum
if (messH = (*fromTocH)->sums[fromSumNum].messH)
{
messWin = (*messH)->win;
messWinWP = GetMyWindowWindowPtr(messWin);
UsingWindow(messWinWP);
}
// if the message is not open, open it.
openedMessage = false;
if (messWin == nil)
{
if (messWin = GetAMessage(fromTocH, fromSumNum, nil, nil, false))
{
messWinWP = GetMyWindowWindowPtr(messWin);
messH = (*fromTocH)->sums[fromSumNum].messH;
openedMessage = true; // keep track that we were the ones that opened this message
}
}
// providing we've opened the pop message to transfer ...
if (messWin)
{
// create a new spool file for this message
if ((err=SubFolderSpec(SPOOL_FOLDER,spoolSpec))==noErr)
{
spoolSpec->parID = SpecDirId(spoolSpec);
EnsureMID(fromTocH,fromSumNum);
ComposeRString(spoolSpec->name,IMAP_SPOOL_FMT,(*fromTocH)->sums[fromSumNum].msgIdHash);
UniqueSpec(spoolSpec,31);
// spool the message into a new file
err = SpoolMessage(messH, spoolSpec, 0);
// remember the state, flags, and serialNum of the message
spoolData->fromState = (*fromTocH)->sums[fromSumNum].state;
spoolData->fromFlags = (*fromTocH)->sums[fromSumNum].flags;
spoolData->serialNum = (*fromTocH)->sums[fromSumNum].serialNum;
// also remember where this message came from
spoolData->mailbox = (*fromTocH)->mailbox.spec;
if (err == noErr) if (CommandPeriod) err = userCanceledErr;
}
// close the window if we need to ...
if (openedMessage)
CloseMyWindow(messWinWP);
else
NotUsingWindow(messWinWP);
}
else err = -1;
return (err);
}
/************************************************************************
* DoTransferMessagesToServer - upload spool files to the IMAP server
************************************************************************/
OSErr DoTransferMessagesToServer(TOCHandle toTocH, IMAPAppendHandle spoolData, Boolean copy, Boolean forceForeground)
{
OSErr err = noErr;
PersHandle oldPers = CurPers;
PersHandle toPers = nil;
MailboxNodeHandle toBox = nil;
IMAPStreamPtr imapStream = nil;
Str255 toUser, toHost;
short numMessages = GetHandleSize_(spoolData)/sizeof(IMAPAppendStruct);
short count;
Str255 progressMessage;
MyWindowPtr messWin = nil;
MessHandle messH = nil;
Boolean openedMessage = false;
// must have a destination
if (!toTocH)
{
err = IMAPParamErr;
goto done;
}
// must have a list of POP messages to transfer
if (!numMessages || !spoolData)
{
err = IMAPParamErr;
goto done;
}
// must be online
if (Offline && GoOnline())
{
err = OFFLINE;
goto done;
}
// figure out where the destination mailbox is
toBox = TOCToMbox(toTocH);
toPers = TOCToPers(toTocH);
if ((CurPers=toPers) && PrefIsSet(PREF_IS_IMAP))
{
GetPOPInfo(toUser,toHost);
// be progressive
PROGRESS_START;
PROGRESS_MESSAGER(kpTitle,copy ? IMAP_COPY_MESSAGES : IMAP_TRANSFER_MESSAGES);
// allocate the stream we'll need to talk to this mailbox
PROGRESS_MESSAGER(kpSubTitle,IMAP_LOGGING_IN);
imapStream = GetIMAPConnection(IMAPTransferTask, CAN_PROGRESS);
if (imapStream)
{
// open the destination mailbox
LockMailboxNodeHandle(toBox);
if (IMAPOpenMailbox(imapStream, (*toBox)->mailboxName,false))
{
for (count = 0; (count < numMessages) && (err == noErr); count++)
{
// indicate which message we're transferring
ComposeRString(progressMessage,POP_STATUS_FMT, count + 1, numMessages);
PROGRESS_MESSAGE(kpSubTitle,progressMessage);
// Append the message to the destination mailbox
LDRef(spoolData);
err = AppendSpoolFile(imapStream, &(*spoolData)[count]);
UL(spoolData);
if (err == noErr) if (CommandPeriod) err = userCanceledErr;
// rememeber that the message was successfully transferred
if (err == noErr)
{
(*spoolData)[count].transferred = true;
IMAPMailboxChanged(toBox);
}
else IMAPError(kIMAPTransfer, err, errIMAPCopyFailed);
}
}
else
{
if (CommandPeriod) err = userCancelled;
else IE(imapStream, kIMAPTransfer, err=kIMAPSelectMailboxErr, errIMAPSelectMailbox);
}
UnlockMailboxNodeHandle(toBox);
// close the connection
CleanupConnection(&imapStream);
// wipe out all the spool specs
LDRef(spoolData);
for (count = 0; (count < numMessages);count++)
FSpDelete(&(*spoolData)[count].spoolSpec);
UL(spoolData);
// remember the POP messages so we can delete them from the main thread ...
if ((err == noErr) && !copy)
{
// wait for the handle listing POP messages to be deleted is not in use
while (gSpoolDataLocked)
{
while (!CommandPeriod && gSpoolDataLocked)
{
CycleBalls();
if (MyYieldToAnyThread()) break;
}
}
// remember the list of spool specs so we can kill the POP messages later ...
if (!CommandPeriod)
{
if (gSpoolData==nil) gSpoolData = NuHandle(0);
if (HandPlusHand(spoolData, gSpoolData)==noErr)
{
spoolData = nil;
}
else
{
ZapHandle(gSpoolData);
}
}
// if we're in the foreground, remove the mesage from the POP mailbox now.
if (forceForeground) UpdatePOPMailboxes();
}
// and resync the destination mailbox
if ((*toTocH)->imapTOC)
{
// No matter what the outcome is, resync the destination mailbox to reflect any changes we may have made
if (IMAPFilteringUnderway()) SetIMAPMailboxNeeds(toBox, kNeedsResync, true);
else
{
Boolean oldCommandPeriod = CommandPeriod;
// start a resync if we're not in the process of filtering.
CommandPeriod = false;
if ((*toTocH)->win && IsWindowVisible(GetMyWindowWindowPtr((*toTocH)->win))) FetchNewMessages(toTocH, true, false, false, false);
CommandPeriod = CommandPeriod | oldCommandPeriod;
}
}
}
else
{
WarnUser(MEM_ERR, err);
}
}
// Cleanup any leftovers
CurPers = oldPers;
PROGRESS_END;
if (spoolData) ZapHandle(spoolData);
done:
IMAPTocHBusy(toTocH, false);
return (err);
}
/************************************************************************
* UpdatePOPMailboxes - update any POP mailboxes that have been
* tormented by IMAP
************************************************************************/
void UpdatePOPMailboxes(void)
{
short numMessages = gSpoolData ? GetHandleSize_(gSpoolData)/sizeof(IMAPAppendStruct) : 0;
short delSum;
TOCHandle tocH;
if (gSpoolData && numMessages)
{
gSpoolDataLocked = true; // don't let anybody muck with this
while (numMessages)
{
// was this message successfully transferred?
if ((*gSpoolData)[numMessages-1].transferred)
{
// find the TOC
tocH = TOCBySpec(&((*gSpoolData)[numMessages-1].mailbox));
if (tocH)
{
// can we find the message in the source toc still?
delSum = FindSumBySerialNum(tocH,(*gSpoolData)[numMessages-1].serialNum);
if ((delSum >= 0) && (delSum < (*tocH)->count))
{
// we don't know where this message is anymore. Remove it from the Search Window.
SearchUpdateSum(tocH, delSum, tocH, (*tocH)->sums[delSum].serialNum, false, true);
// delete it
DeleteMessageLo(tocH, delSum, true);
}
}
}
//next message
numMessages--;
}
ZapHandle(gSpoolData);
gSpoolData = nil;
gSpoolDataLocked = false; // it's open season again
}
}
/************************************************************************
* AppendSpoolFile - given a spec, append it to the currently
* selected mailbox.
************************************************************************/
OSErr AppendSpoolFile(IMAPStreamPtr imapStream, IMAPAppendPtr spoolData)
{
OSErr err = noErr;
Boolean result;
STRING ms;
long totalSize = 0;
long seconds = 0;
STRINGFileStruct msFile;
char *flags = nil;
Boolean sent = spoolData->fromFlags&FLAG_OUT ? true : false; // set the /Sent flag if this message comes from the Out box.
// set up the STRINGFileStruct we'll use to read the spool file
Zero(msFile);
msFile.buffer = NuPtr(GetRLong(IMAP_TRANSFER_BUFFER_SIZE) + 4);
if (msFile.buffer)
{
msFile.bufferSize = GetRLong(IMAP_TRANSFER_BUFFER_SIZE);
SimpleMakeFSSpec(spoolData->spoolSpec.vRefNum, spoolData->spoolSpec.parID, spoolData->spoolSpec.name, &(msFile.spoolSpec));
msFile.bytesRead = 0;
// Get the total size of the message to be transferred
if ((totalSize = FSpFileSize(&(spoolData->spoolSpec))) > 0)
{
// Initialize the STRING.
Zero(ms);
ms.dtb = &FileAppendDriver;
ms.data = (void *)&msFile;
(ms.dtb)->init(&ms, nil, totalSize);
// the destination message will have the same flags as the source
FlagsString(&flags, (spoolData->fromState != UNREAD), false, false, (spoolData->fromState == REPLIED), false, true, sent);
// now actually go do the append
result = IMAPAppendMessage(imapStream, flags, seconds, &ms);
// display an error if the append failed.
if (!result) IMAPError(kIMAPTransfer, kIMAPCopyErr, err=errIMAPCopyFailed);
}
// get rid of the flags string
if (flags) ZapPtr(flags);
// get rid of the transfer buffer
ZapPtr(msFile.buffer);
}
else
{
// failed to allocate buffer
WarnUser(MEM_ERR, err=MemError());
}
return (err);
}
/************************************************************************
* CopyMessages - copy messages from the SELECTed mailbox to the
* destination mailbox.
************************************************************************/
Boolean CopyMessages(IMAPStreamPtr imapStream, MailboxNodeHandle mbox, Handle uids, Boolean copy, Boolean silent)
{
Boolean result = true;
Str255 uidStr;
unsigned long uid;
short numMessages = GetHandleSize(uids)/sizeof(unsigned long);
short i, j, first, sumNum;
MailboxNodeHandle fromMBox;
TOCHandle fromTocH, hidTocH = NULL;
long numCopies = 0;
UIDCopyPtr ucptr;
DeliveryNodeHandle node;
Boolean setup = false;
// must be connected, and have a mailbox SELECTed.
if (!imapStream || !IsSelected(imapStream->mailStream)) return (false);
// Sort the list of messages to be copied for maximum performance
if (!PrefIsSet(PREF_IMAP_DONT_USE_UID_RANGE)) SortUIDListHandle(uids);
// UIDPlus stuff.
if (!PrefIsSet(PREF_NO_USE_UIDPLUS))
{
// locate the source mailbox. Maybe this should be stored within the imapStream, Einstein.
fromMBox = LocateNodeByMailboxName((*CurPers)->mailboxTree, imapStream->mailboxName);
if (fromMBox)
{
fromTocH = TOCBySpec(&(*fromMBox)->mailboxSpec);
if (fromTocH)
{
// create a delivery node for the UIDPLUS responses.
node = NewDeliveryNode(fromTocH);
// set up the node with the proper things, include the original UIDs and enough room for the new UIDs.
if (node)
{
// Allocate a new UIDPLUS response item in the delivery node
if ((*node)->tc == NULL)
(*node)->tc = NuHandleClear(sizeof(UIDCopyStruct));
else
{
// some copies are already pending
numCopies = GetHandleSize((*node)->tc);
SetHandleBig_((*node)->tc,(numCopies + 1)*sizeof(UIDCopyStruct));
}
if (MemError() != noErr)
ZapHandle((*node)->tc);
// Store the interesting bits about this copy
ucptr = &((UIDCopyPtr)(*((*node)->tc)))[numCopies];
if ((*node)->tc != NULL)
{
ucptr->toSpec = (*mbox)->mailboxSpec;
ucptr->copy = copy;
// store copies of the old summaries. We need to do this becuase the sums may
// change between now and when we get around to transferring the local cache.
ucptr->hOldSums = NewHandleClear(numMessages*sizeof(MSumType));
if (MemError() == noErr)
{
for (i=0; i<numMessages; i++)
{
sumNum = UIDToSumNum(((unsigned long *)(*uids))[i], fromTocH);
if (sumNum != -1)
CopySum(&((*fromTocH)->sums[sumNum]), &(((MSumPtr)(*(ucptr->hOldSums)))[i]), 0);
else
{
// look in the hidden toc for the original message
if (hidTocH || ((hidTocH = GetHiddenCacheMailbox(TOCToMbox(fromTocH), false, false))!=NULL))
{
sumNum = UIDToSumNum(((unsigned long *)(*uids))[i], hidTocH);
if (sumNum != -1)
CopySum(&((*hidTocH)->sums[sumNum]), &(((MSumPtr)(*(ucptr->hOldSums)))[i]), 0);
}
}
}
// Allocate enough room for the new ones
ucptr->hNewUIDs = NewHandleClear(numMessages*sizeof(unsigned long));
if (MemError() == noErr)
setup = true;
}
}
}
}
}
}
// iterate through (unordered) list of uids, copying each one to the destination mailbox
PROGRESS_MESSAGER(kpSubTitle,LEFT_TO_TRANSFER);
for (i=0; i<numMessages && result; i++)
{
PROGRESS_BAR((100*i)/numMessages,numMessages - i,nil,nil,nil);
first = i;
// build the range string for the command ...
if (PrefIsSet(PREF_IMAP_DONT_USE_UID_RANGE))
{
uid = ((unsigned long *)(*uids))[i];
sprintf (uidStr,"%lu",uid);
}
else
i = GenerateUIDString(uids, i, &uidStr);
LockMailboxNodeHandle(mbox);
result = UIDCopy(imapStream, uidStr, (*mbox)->mailboxName);
UnlockMailboxNodeHandle(mbox);
// Store the UIDPLUS responses
// finally, update the cache if we can
if (!PrefIsSet(PREF_NO_USE_UIDPLUS) && result && imapStream && imapStream->mailStream)
{
// Do we have the latest information on the destination mailbox?
if (setup && (((*mbox)->uidValidity == 0) || (imapStream->mailStream->UIDPLUSuv == (*mbox)->uidValidity)))
{
AccuTrim(&imapStream->mailStream->UIDPLUSResponse);
// move these responses to the delivery node
for (j = 0; first <= i; first++)
((unsigned long *)*(ucptr->hNewUIDs))[first] = ((unsigned long *)*(imapStream->mailStream->UIDPLUSResponse.data))[j++];
AccuZap(imapStream->mailStream->UIDPLUSResponse);
}
else
{
// Either the UIDValidity has changed, or we don't have any responses.
setup = false;
}
}
// display an error if the copy failed.
if (!result && !silent)
IMAPError(kIMAPTransfer, kIMAPCopyErr, errIMAPCopyFailed);
}
PROGRESS_BAR(100,0,nil,nil,nil);
// if the copies succeeded, mark the messages as deleted as well.
if (result && !copy)
{
for (i=0; (i<numMessages) && result; i++)
{
// build the range string for the command ...
if (PrefIsSet(PREF_IMAP_DONT_USE_UID_RANGE))
{
uid = ((unsigned long *)(*uids))[i];
sprintf (uidStr,"%lu",uid);
}
else
i = GenerateUIDString(uids, i, &uidStr);
// mark the message for deletion. Do not expunge here.
result = UIDDeleteMessages(imapStream, uidStr, false);
}
}
// finally, make the UIDPLUS responses available. Or Zap 'em if something went wrong.
if (!PrefIsSet(PREF_NO_USE_UIDPLUS))
{
if (result && node && setup)
{
// we're done!
QueueDeliveryNode(node);
(*node)->finished = true;
}
else // something went wrong setting up ...
ZapDeliveryNode(&node);
}
return (result);
}
/************************************************************************
* GenerateUIDString - given some uids, and an index into them, generate
* a string in the form of "x:y" (or just "x")
************************************************************************/
short GenerateUIDString(Handle uids, short index, Str255 uidStr)
{
unsigned long uid, lastUid, nextUid;
short numMessages = GetHandleSize(uids)/sizeof(unsigned long);
uid = ((unsigned long *)(*uids))[index];
lastUid = uid;
// Is there a message after this one?
if (index < (numMessages - 1))
{
nextUid = ((unsigned long *)(*uids))[index+1];
lastUid = uid;
// Increment through the list until we find a UID out of sequence
while (((nextUid - lastUid) == 1) && (index < (numMessages - 1)))
{
index++;
lastUid = nextUid;
nextUid = ((unsigned long *)(*uids))[index+1];
}
}
if (lastUid != uid)
{
// There were some messages in a row with sequential UIDs.
sprintf (uidStr,"%lu:%lu",uid,lastUid);
}
else
{
// operate on only a single message
sprintf (uidStr,"%lu",uid);
}
return (index);
}
/************************************************************************
* GenerateUIDStringFromUIDNodeHandle - the same, only now for UID
* lists.
************************************************************************/
short GenerateUIDStringFromUIDNodeHandle(UIDNodeHandle uids, short index, unsigned long *first, unsigned long *last, Str255 uidStr)
{
unsigned long uid, lastUid, nextUid;
short numMessages = GetUIDNodeCount(uids);
UIDNodeHandle node;
long n;
if (index <= numMessages)
{
node = uids;
for (n = 0; n < index; n++) node = (*node)->next;
uid = (*node)->uid;
lastUid = uid;
// Is there a message after this one?
if (index < (numMessages - 1))
{
node = (*node)->next;
nextUid = (*node)->uid;
lastUid = uid;
// Increment through the list until we find a UID out of sequence
while ((node) && ((nextUid - lastUid) == 1) && (index < (numMessages - 1)))
{
index++;
lastUid = nextUid;
node = (*node)->next;
if (node) nextUid = (*node)->uid;
}
}
}
if (lastUid != uid)
{
// There were some messages in a row with sequential UIDs.
sprintf (uidStr,"%lu:%lu",uid,lastUid);
}
else
{
// operate on only a single message
sprintf (uidStr,"%lu",uid);
}
*first = uid;
*last = lastUid;
return (index);
}
/**********************************************************************
* UIDListCompare - compare two entries
**********************************************************************/
int UIDListCompare(unsigned long *uid1, unsigned long *uid2)
{
return (*uid1 - *uid2);
}
/**********************************************************************
* SwapUID - swap two uid entries.
**********************************************************************/
void SwapUID(unsigned long *uid1, unsigned long *uid2)
{
unsigned long tempUid;
tempUid = *uid1;
*uid1 = *uid2;
*uid2 = tempUid;
}
/************************************************************************
* SortUIDListHandle - sort a handle packed with UIDs
************************************************************************/
void SortUIDListHandle(Handle toSort)
{
short count = 0;
SignedByte state;
unsigned long realBig = REAL_BIG;
state = HGetState(toSort);
count = GetHandleSize(toSort)/sizeof(unsigned long);
PtrPlusHand_(&realBig,toSort,sizeof(unsigned long));
QuickSort((void*)LDRef(toSort),sizeof(unsigned long),0,count-1,(void*)UIDListCompare,(void*)SwapUID);
SetHandleBig_(toSort,count*sizeof(unsigned long));
HSetState(toSort, state);
}
/************************************************************************
* UpdateLocalSummaries - remove the summaries from a mailbox window.
* If !expunge, just mark the specified messages as deleted.
************************************************************************/
OSErr UpdateLocalSummaries(TOCHandle fromTocH, Handle uids, Boolean undelete, Boolean expunge, Boolean cleanupAttachments)
{
OSErr err = noErr;
DeliveryNodeHandle node = nil;
short numUids = GetHandleSize(uids)/sizeof(unsigned long);
short sumNum, count;
MSumType sum;
SignedByte state;
// mus thave a list of UIDs to work with, and a toc to transfer from.
if (numUids && fromTocH)
{
// set up delete request.
node = NewDeliveryNode(fromTocH);
if (node)
{
(*node)->filter = false;
// expunge? Then remove the summaries from the sourcemailbox
if (expunge)
{
state = HGetState((Handle)node);
LDRef(node);
(*node)->td = NuHandle(numUids*sizeof(MSumType));
HSetState((Handle)node, state);
if ((*node)->td)
{
Zero(sum);
(*node)->cleanupAttachments = cleanupAttachments;
// scan through fromTocH, copying summaries from there to the new tocH
for (count=0;count<numUids;count++)
{
sum.uidHash = ((unsigned long *)(*uids))[count];
BMD(&sum,&((MSumPtr)(*((*node)->td)))[count],sizeof(MSumType));
}
(*node)->finished = true;
TOCSetDirty(fromTocH,true);
QueueDeliveryNode(node);
}
else
{
IMAPError(kIMAPUpdateLocal, kIMAPMemErr, err=MemError());
ZapHandle(node);
}
}
// just mark as deleted.
else
{
state = HGetState((Handle)node);
LDRef(node);
(*node)->tu = NuHandle(numUids*sizeof(MSumType));
HSetState((Handle)node, state);
if ((*node)->tu)
{
Zero(sum);
// scan through fromTocH, copying summaries from there to the new tocH
for (count=0;count<numUids;count++)
{
for (sumNum=0;sumNum<(*fromTocH)->count;sumNum++)
{
if ((*fromTocH)->sums[sumNum].uidHash == ((unsigned long *)(*uids))[count])
{
sum.uidHash = (*fromTocH)->sums[sumNum].uidHash;
if (undelete)
{
sum.opts = (*fromTocH)->sums[sumNum].opts & ~OPT_DELETED;
}
else
{
sum.opts = (*fromTocH)->sums[sumNum].opts | OPT_DELETED;
}
sum.state = (*fromTocH)->sums[sumNum].state;
BMD(&sum,&((MSumPtr)(*((*node)->tu)))[count],sizeof(MSumType));
sumNum = (*fromTocH)->count;
}
}
}
(*node)->finished = true;
TOCSetDirty(fromTocH,true);
QueueDeliveryNode(node);
}
else
{
IMAPError(kIMAPUpdateLocal, kIMAPMemErr, err=MemError());
ZapHandle(node);
}
}
}
else
{
IMAPError(kIMAPUpdateLocal, kIMAPMemErr, err=MemError());
}
}
return (err);
}
/************************************************************************
* Set up a thread to expunge a toc.
************************************************************************/
Boolean IMAPRemoveDeletedMessages(TOCHandle tocH)
{
MailboxNodeHandle mailboxNode = nil;
Boolean result = false;
OSErr err = noErr;
XferFlags flags;
IMAPTransferRec imapInfo;
short sum;
// must have a toc
if (tocH == nil) return (false);
// we must be online
if (Offline && GoOnline()) return(OFFLINE);
// are there messages in the mailbox to be deleted?
if (CountDeletedIMAPMessages(tocH) == 0) return (true);
// first, go close all the open messages in this toc that are to be deleted
for (sum=0;sum<(*tocH)->count;sum++)
{
if (((*tocH)->sums[sum].opts&OPT_DELETED) && ((*tocH)->sums[sum].messH))
{
CloseMyWindow(GetMyWindowWindowPtr((*(*tocH)->sums[sum].messH)->win));
}
}
if (PrefIsSet(PREF_THREADING_OFF) || !ThreadsAvailable())
{
result = DoExpungeMailbox(tocH);
}
else
{
PersHandle oldPers = CurPers;
// figure out who this mailbox belongs to
mailboxNode = TOCToMbox(tocH);
CurPers = TOCToPers(tocH);
// collect password now if we need it.
err = CheckIMAPSettingsForPers();
// if we were given a password, set up the thread
if (err == noErr)
{
Zero(flags);
Zero(imapInfo);
imapInfo.delToc = tocH;
imapInfo.command = IMAPExpungeTask;
err = SetupXferMailThread (false, false, true, false, flags, &imapInfo);
if (err == noErr) result = true;
// remove any old task errors
RemoveTaskErrors(IMAPExpungeTask,(*CurPers)->persId);
// forget the keychain password so it's not written to the settings file
if (KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN)) Zero((*CurPers)->password);
}
CurPers = oldPers;
}
return (result);
}
/************************************************************************
* DoExpungeMailbox - remove the deleted messages from an mbox
************************************************************************/
Boolean DoExpungeMailbox(TOCHandle tocH)
{
// EXPUNGE the mailbox only if there's messages marked for deleted locally.
return (DoExpungeMailboxLo(tocH, true));
}
/************************************************************************
* DoExpungeMailbox - remove the deleted messages from an mbox
************************************************************************/
Boolean DoExpungeMailboxLo(TOCHandle tocH, Boolean bCheckLocal)
{
Boolean result = false;
IMAPStreamPtr imapStream;
PersHandle oldPers = CurPers;
MailboxNodeHandle mailboxNode = nil;
Str255 progressMessage, mName;
Handle uids = nil;
short count, sumNum=0;
TOCHandle hidTocH;
// must have a tocH
if (!tocH) return (false);
// and it must belong to an IMAP personality
mailboxNode = TOCToMbox(tocH);
CurPers = TOCToPers(tocH);
// might need to look at the hidden toc as well
hidTocH = GetHiddenCacheMailbox(mailboxNode, false, false);
if (mailboxNode && CurPers)
{
//
// scan through the tocH, seeing which uids are going to be expunged
//
// how many messages are marked for deletion?
sumNum = CountDeletedIMAPMessages(tocH);
// are some messages marked to be deleted? Or do we not care?
if (sumNum || !bCheckLocal)
{
// figure out which ones ...
uids = NuHandle(sumNum*sizeof(unsigned long));
if (uids)
{
// check out the toc for deleted messages. We'll remove them once the EXPUNGE completes.
for (count=0;(count<(*tocH)->count) && (sumNum > 0);count++)
{
if ((*tocH)->sums[count].opts&OPT_DELETED)
{
((unsigned long *)(*uids))[sumNum-1] = (*tocH)->sums[count].uidHash;
sumNum--;
}
}
// check out the hidden toc as well.
for (count=0;hidTocH && (count<(*hidTocH)->count) && (sumNum > 0);count++)
{
if ((*hidTocH)->sums[count].opts&OPT_DELETED)
{
((unsigned long *)(*uids))[sumNum-1] = (*hidTocH)->sums[count].uidHash;
sumNum--;
}
}
//
// connect and do the actual expunge
//
// display some progress indicating which mailbox we're going to expunge
if (InAThread())
{
LockMailboxNodeHandle(mailboxNode);
PathToMailboxName ((*mailboxNode)->mailboxName, mName, (*mailboxNode)->delimiter);
UnlockMailboxNodeHandle(mailboxNode);
ComposeRString(progressMessage,IMAP_EXPUNGE_MAILBOX,mName);
PROGRESS_START;
PROGRESS_MESSAGE(kpTitle,progressMessage);
PROGRESS_MESSAGER(kpSubTitle,IMAP_LOGGING_IN);
}
// Create a new IMAP stream
imapStream = GetIMAPConnection(IMAPExpungeTask, CAN_PROGRESS);
if (imapStream)
{
LockMailboxNodeHandle(mailboxNode);
if (IMAPOpenMailbox(imapStream, (*mailboxNode)->mailboxName,false))
{
if (result = Expunge(imapStream))
{
// remove these messages from the tocH on screen
// note, this will take care of the hidden toc as well.
UpdateLocalSummaries(tocH, uids, false, true, false);
}
else // couldn't expunge. We're read only or something right now.
IMAPError(kIMAPExpunge, kIMAPExpungeMailbox, errIMAPCantExpunge);
}
UnlockMailboxNodeHandle(mailboxNode);
// No longer need to consider this mailbox in auto expunge. Even if we failed above.
SetIMAPMailboxNeeds(mailboxNode, kNeedsAutoExp, false);
CleanupConnection(&imapStream);
}
ZapHandle(uids);
}
else
{
IMAPError(kIMAPExpunge, kIMAPMemErr, MemError());
}
}
// else
// no summaries are marked for deletion.
}
else // could not open mailbox.
IMAPError(kIMAPExpunge, kIMAPNotIMAPMailboxErr, errNotIMAPMailboxErr);
CurPers = oldPers;
return (result);
}
/************************************************************************
* StreamAppendDriverInit - initialize a STRING and read the first bit
************************************************************************/
static void StreamAppendDriverInit(STRING *s, void *data, unsigned long size)
{
#pragma unused(data)
unsigned long first, nBytes;
unsigned long chunksize = 0;
STRINGDataStructPtr msDataPtr = (STRINGDataStructPtr)(s->data);
// makes no sense to do anything if we don't have a string
if (!s || !msDataPtr) return;
// "size" is the rfc822 size of the message. Set it and don't change it
s->size = size;
// Set STRING to point to the buffer in the file reader.
s->chunk = s->curpos = msDataPtr->buffer;
// Nothing in the buffer yet.
s->chunksize = s->cursize = 0;
// Go read the first chunk from the source message
first = 0;
nBytes = MIN(s->size, msDataPtr->bufferSize);
// Read:
msDataPtr->result = UIDFetchPartialContentsToBuffer(msDataPtr->imapStream, msDataPtr->uid, nil, first, nBytes, msDataPtr->buffer, msDataPtr->bufferSize, &chunksize);
s->cursize = s->chunksize = chunksize;
// Catch error.
if (s->cursize <= 0)
{
s->cursize = 0;
s->chunksize = 0;
}
// Increment bytes read
msDataPtr->bytesRead += s->cursize;
// never set offset
s->data1 = s->offset = 0;
}
/************************************************************************
* StreamAppendDriverNext - grab the next chunk of a message on a server
************************************************************************/
static char StreamAppendDriverNext (STRING *s)
{
unsigned long first, nBytes;
STRINGDataStructPtr msDataPtr = (STRINGDataStructPtr)(s->data);
// Make sure we have a string
if (!s || !msDataPtr) return (nil);
// Re-initiallize bits of the string
s->chunk = s->curpos = msDataPtr->buffer;
s->cursize = 0;
*s->chunk = 0;
// Fetch next chunk.
first = msDataPtr->bytesRead;
if (msDataPtr->bytesRead >= s->size) nBytes = 0;
else nBytes = MIN(msDataPtr->bufferSize, s->size - msDataPtr->bytesRead);
if (nBytes <= 0)
{
// Error.
s->cursize = 0;
s->curpos = NULL;
return (nil);
}
// Go read another chunk-full from the message.
UIDFetchPartialContentsToBuffer(msDataPtr->imapStream, msDataPtr->uid, nil, first, nBytes, msDataPtr->buffer, msDataPtr->bufferSize, &(s->cursize));
// Did we get something back?
if (s->cursize < 0)
{
// Error.
s->cursize = 0;
s->curpos = nil;
}
// Increment bytes read
msDataPtr->bytesRead += s->cursize;
return (*s->chunk);
}
/************************************************************************
* StreamAppendDriverSetpos - update the current position.
************************************************************************/
static void StreamAppendDriverSetpos (STRING *s, unsigned long i)
{
if (s)
{
s->curpos = s->chunk + i;
s->cursize = s->chunksize - i;
}
}
/************************************************************************
* FileAppendDriverInit - initialize a STRING and read the first bit
************************************************************************/
static void FileAppendDriverInit(STRING *s, void *data, unsigned long size)
{
#pragma unused(data)
unsigned long first, nBytes;
STRINGFileStructPtr msDataPtr = (STRINGFileStructPtr)(s->data);
short refN;
OSErr err;
// makes no sense to do anything if we don't have a string
if (!s || !msDataPtr) return;
// "size" is the size of the spooled message. Set it and don't change it
s->size = size;
// Set STRING to point to the buffer in the file reader.
s->chunk = s->curpos = msDataPtr->buffer;
// Nothing in the buffer yet.
s->chunksize = s->cursize = 0;
// Go read the first chunk from the source message
first = 0;
nBytes = MIN(s->size, msDataPtr->bufferSize);
// Read:
if ((err=AFSpOpenDF(&msDataPtr->spoolSpec,&msDataPtr->spoolSpec,fsRdPerm,&refN))==noErr)
{
if ((err=ARead(refN,&nBytes,msDataPtr->buffer))!=noErr)
{
// clear out the buffer if there was an error
WriteZero(msDataPtr->buffer, msDataPtr->bufferSize);
}
MyFSClose(refN);
}
s->cursize = s->chunksize = nBytes;
// Catch error.
if (s->cursize <= 0)
{
s->cursize = 0;
s->chunksize = 0;
}
// Increment bytes read
msDataPtr->bytesRead += s->cursize;
// never set offset
s->data1 = s->offset = 0;
}
/************************************************************************
* StreamAppendDriverNext - grab the next chunk of a message from a file
************************************************************************/
static char FileAppendDriverNext(STRING *s)
{
unsigned long first, nBytes;
STRINGFileStructPtr msDataPtr = (STRINGFileStructPtr)(s->data);
OSErr err = noErr;
short refN;
// Make sure we have a string
if (!s || !msDataPtr) return (nil);
// Re-initiallize bits of the string
s->chunk = s->curpos = msDataPtr->buffer;
s->cursize = 0;
*s->chunk = 0;
// Fetch next chunk.
first = msDataPtr->bytesRead;
if (msDataPtr->bytesRead >= s->size) nBytes = 0;
else nBytes = MIN(msDataPtr->bufferSize, s->size - msDataPtr->bytesRead);
if (nBytes <= 0)
{
// Error.
s->cursize = 0;
s->curpos = NULL;
return (nil);
}
// Go read another chunk-full from the message.
if (!(err=AFSpOpenDF(&msDataPtr->spoolSpec,&msDataPtr->spoolSpec,fsRdPerm,&refN)))
{
if (!(err=SetFPos(refN,fsFromStart,msDataPtr->bytesRead)))
{
err = ARead(refN,&nBytes,msDataPtr->buffer);
}
MyFSClose(refN);
}
// clear out the buffer if there was an error
if (err != noErr) WriteZero(msDataPtr->buffer, msDataPtr->bufferSize);
s->cursize = s->chunksize = nBytes;
// Did we get something back?
if (s->cursize < 0)
{
// Error.
s->cursize = 0;
s->curpos = nil;
}
// Increment bytes read
msDataPtr->bytesRead += s->cursize;
return (*s->chunk);
}
/************************************************************************
* StreamAppendDriverSetpos - update the current position.
************************************************************************/
static void FileAppendDriverSetpos(STRING *s, unsigned long i)
{
if (s)
{
s->curpos = s->chunk + i;
s->cursize = s->chunksize - i;
}
}
/************************************************************************
* IMAPMessageDownloaded - return true is a given summary has been dld
************************************************************************/
Boolean IMAPMessageDownloaded(TOCHandle t, short s)
{
return ((*t)->sums[s].offset >= 0);
}
/************************************************************************
* IMAPMessageBeingDownloaded - return true if a summary needs to be dld
************************************************************************/
Boolean IMAPMessageBeingDownloaded(TOCHandle tocH, short s)
{
Boolean onItsWay = false;
MailboxNodeHandle mailboxNode;
PersHandle oldPers = CurPers;
threadDataHandle index;
short numUids;
SignedByte state;
// it's not being downloaded if it's not an IMAP message
if (!(*tocH)->imapTOC) return (false);
// it's not being downloaded if it's already here
if ((*tocH)->sums[s].offset >= 0) return (false);
// if the message lives in an IMAP mailbox ...
if (!onItsWay)
{
mailboxNode = TOCToMbox(tocH);
CurPers = TOCToPers(tocH);
if (mailboxNode && CurPers)
{
// and the mailbox is set up to receive message bodies during a resync ...
if (PrefIsSet(PREF_IMAP_FULL_MESSAGE_AND_ATTACHMENTS) || PrefIsSet(PREF_IMAP_FULL_MESSAGE))
{
// and there's a resync underway ...
if (FindNodeByToc(tocH))
{
// then the message is already being downloaded.
onItsWay = true;
}
}
}
CurPers = oldPers;
}
// look at all fetching tasks and see if this summary is being fetched currently
if (!onItsWay)
{
// are there any threads currently downloading this message?
for (index=gThreadData;index && !onItsWay;index=(*index)->next)
{
if ((*index)->currentTask == IMAPFetchingTask)
{
state = HGetState((Handle)index);
LDRef(index);
if (SameTOC((*index)->imapInfo.destToc, tocH))
{
if ((*index)->imapInfo.uids)
{
for (numUids = 0; numUids < (GetHandleSize((*index)->imapInfo.uids)/sizeof(unsigned long)); numUids++)
{
if (((unsigned long *)(*((*index)->imapInfo.uids)))[numUids] == (*tocH)->sums[s].uidHash)
{
onItsWay = true;
}
}
}
}
HSetState((Handle)index,state);
}
}
}
return (onItsWay);
}
/************************************************************************
* IMAPAbortMessageFetch - cancel a message download that's underway
************************************************************************/
void IMAPAbortMessageFetch(TOCHandle tocH, short sumNum)
{
threadDataHandle index;
short numUids;
SignedByte state;
// must have a TOC
if (!tocH) return;
// must have a summary
if (sumNum < 0 || sumNum > (*tocH)->count) return;
// nothing to do if this is not an IMAP toc ...
if (!(*tocH)->imapTOC) return;
// no need to cancel if this message isn't even being downloaded.
if (!IMAPMessageBeingDownloaded(tocH, sumNum)) return;
// don't cancel the download if this message is being previewd
if (IMAPMessageBeingPreviewed(tocH, sumNum)) return;
// don't cancel the download if the message window is open anywhere.
if ((*tocH)->sums[sumNum].messH && (*(*tocH)->sums[sumNum].messH)->win) return;
// are there any threads currently downloading this message?
for (index=gThreadData;index;index=(*index)->next)
{
if ((*index)->currentTask == IMAPFetchingTask)
{
state = HGetState((Handle)index);
LDRef(index);
if (SameTOC((*index)->imapInfo.destToc, tocH))
{
if ((*index)->imapInfo.uids)
{
for (numUids = 0; numUids < (GetHandleSize((*index)->imapInfo.uids)/sizeof(unsigned long)); numUids++)
{
if (((unsigned long *)(*((*index)->imapInfo.uids)))[numUids] == (*tocH)->sums[sumNum].uidHash)
{
// cancel the thread
SetThreadGlobalCommandPeriod ((*index)->threadID, true);
HSetState((Handle)index,state);
break;
}
}
}
}
HSetState((Handle)index,state);
}
}
}
/************************************************************************
* IMAPMessageBeingPreviewed - is this IMAP message in a preview pane?
************************************************************************/
Boolean IMAPMessageBeingPreviewed(TOCHandle tocH, short sumNum)
{
Boolean previewed = false;
TOCHandle curToc, realTocH;
short curSum, realSumNum;
WindowPtr winWP;
// must have a TOC
if (!tocH) return false;
// must have a summary
if (sumNum < 0 || sumNum > (*tocH)->count) return false;
// if this message is being previewed in this TOC, nothing special to do.
if ((*tocH)->previewID == (*tocH)->sums[sumNum].serialNum)
{
previewed = true;
}
else
{
// go through all open windows ...
for (winWP=FrontWindow_();winWP && !previewed;winWP=GetNextWindow(winWP))
{
// only look at search windows ...
if (IsSearchWindow(winWP))
{
// get the toc that this search window uses
GetSearchTOC(GetWindowMyWindowPtr(winWP),&curToc);
if (curToc && (*curToc)->virtualTOC)
{
// is this TOC previewing a message?
if ((*curToc)->previewID!=0)
{
// see if it's the same message as the one we're about to close
curSum = FindSumBySerialNum(curToc, (*curToc)->previewID);
if (curSum < (*curToc)->count)
{
if ((realTocH = GetRealTOC(curToc,curSum,&realSumNum)))
{
// if it is, don't stop the music ...
if ((*realTocH)->sums[realSumNum].uidHash == (*tocH)->sums[sumNum].uidHash)
previewed = true;
}
}
}
}
}
}
}
return (previewed);
}
/************************************************************************
* FlagsString - build a string of flags
************************************************************************/
char *FlagsString(char **flags, Boolean seen, Boolean deleted, Boolean flagged, Boolean answered, Boolean draft, Boolean recent, Boolean sent)
{
#pragma unused(recent)
short maxSize = strlen(IMAP_SEEN_STRING_FLAG) + strlen(IMAP_DELETED_STRING_FLAG)
+ strlen(IMAP_FLAGGED_STRING_FLAG) + strlen(IMAP_ANSWERED_STRING_FLAG)
+ strlen(IMAP_DRAFT_STRING_FLAG) + 16;
Boolean first = true;
if (!flags) return (nil);
*flags = (char *)fs_get(maxSize);
if (*flags)
{
strcat(*flags, "(");
if (seen)
{
if (!first) strcat(*flags, " ");
strcat(*flags, IMAP_SEEN_STRING_FLAG);
first = false;
}
if (deleted)
{
if (!first) strcat(*flags, " ");
strcat(*flags, IMAP_DELETED_STRING_FLAG);
first = false;
}
if (flagged)
{
if (!first) strcat(*flags, " ");
strcat(*flags, IMAP_FLAGGED_STRING_FLAG);
first = false;
}
if (answered)
{
if (!first) strcat(*flags, " ");
strcat(*flags, IMAP_ANSWERED_STRING_FLAG);
first = false;
}
if (draft)
{
if (!first) strcat(*flags, " ");
strcat(*flags, IMAP_DRAFT_STRING_FLAG);
first = false;
}
if (sent)
{
Str255 sentFlag;
GetRString(sentFlag, IMAP_SENT_FLAG);
if (sentFlag[0])
{
sentFlag[sentFlag[0] + 1] = 0;
if (!first) strcat(*flags, " ");
strcat(*flags, sentFlag+1);
first = false;
}
}
strcat(*flags, ")");
}
else
{
WarnUser(MEM_ERR, MemError());
}
return (*flags);
}
/************************************************************************
* RsyncCurPersInbox - resync the inbox for the current personality
************************************************************************/
OSErr RsyncCurPersInbox(void)
{
OSErr err = false;
Boolean isImap = false;
MailboxNodeHandle inBox = nil;
// locate the INBOX of this personality
inBox = LocateInboxForPers(CurPers);
// and resync it.
if (inBox)
{
LockMailboxNodeHandle(inBox);
err = GetMailbox(&(*inBox)->mailboxSpec, false);
UnlockMailboxNodeHandle(inBox);
}
return (err);
}
/************************************************************************
* WriteBoundary - write a boundary between parts in a spool file.
************************************************************************/
void IMAPWriteBoundary(IMAPStreamPtr stream, IMAPBODY *body, unsigned long uid, char *section, Boolean outerBoundary)
{
long len = 0;
IMAPBODY *mainBody = nil;
PARAMETER *param = nil;
Boolean foundIt = false;
// if we weren't passed a body, write out the top level boundary
if (!body)
{
mainBody = UIDFetchStructure(stream, uid);
body = mainBody;
}
if (body)
{
// iterate through the body's parameter, looking for BOUNDARY
param = body->parameter;
while (param && !foundIt)
{
len = strlen(param->value);
if (param->attribute && !striscmp(param->attribute,"BOUNDARY"))
{
foundIt = true;
FSWriteP(stream->mailStream->refN,CrLf);
FSWriteP(stream->mailStream->refN,dashes);
AWrite(stream->mailStream->refN,&len,param->value);
if (outerBoundary) FSWriteP(stream->mailStream->refN,dashes);
FSWriteP(stream->mailStream->refN,CrLf);
// if we were passed a section number, write the section's headers, too.
if (section)
{
FetchMIMEHeader(stream, uid, section, 0L);
// FSWriteP(stream->mailStream->refN,CrLf);
}
}
// next parameter
param = param->next;
}
}
// Cleanup
if (mainBody) FreeBodyStructure(mainBody);
}
/************************************************************************
* SaveAttachmentStub - save the information needed to fetch an
* attachment later. Put the directly into the spool file.
************************************************************************/
Boolean SaveAttachmentStub(IMAPStreamPtr stream, unsigned long uid, char *section, IMAPBODY *body)
{
Str255 name, typeString, subTypeString;
ResType type, creator;
MIMEMap mm;
short index = 0;
Str255 scratch, scratch2;
Str255 buffer;
// figure out the name
GetFilenameParameter(body, name);
if (*name)
{
// figure out the type and creator
creator = CREATOR;
type = kFakeAppType;
// turn the subtype into a p-string
subTypeString[0] = strlen(body->subtype);
strncpy(subTypeString+1, body->subtype, MIN(subTypeString[0], sizeof(subTypeString)));
// make a reasonable guess at this part's type and creator
if (FindMIMEMapPtr(BodyTypeCodeToPString(body->type, typeString),subTypeString,name,&mm))
{
type = mm.type;
creator = mm.creator;
}
// don't bother creating a stub at all if this is something we're just going to end up tossing.
if (mm.flags&mmDiscard) return (true);
// make a better guess from x-mac-type and x-mac-creator headers
XMacHeaders(body, &type, &creator);
// if we're dealing with an executable, then pack it. See SafeInfo() in hexbin.c
if (TypeIsOnListWhereAndIndex(type,EXECUTABLE_TYPE_LIST,nil,&index)) type = index ? (kFakeAppType&0xffffff00)|('0'+index) : kFakeAppType;
// write a fake mime header in the spool file so our decoding routines know to decode this into the IMAP Attachments folder.
IMAPWriteBoundary(stream, nil, uid, nil, false);
// write a fake section header to tell the decoder to put this part into an attachment stub
ComposeRString(buffer,MIME_CT_PFMT, InterestHeadStrn+hContentType, GetRString(scratch, MIME_TEXT), GetRString(scratch2, MIME_PLAIN), AttributeStrn+aName, name, CrLf);
FSWriteP(stream->mailStream->refN,buffer);
ComposeRString(buffer,MIME_CT_ANNOTATE, AttributeStrn+aMacType,Long2Hex(scratch,type), CrLf);
FSWriteP(stream->mailStream->refN,buffer);
ComposeRString(buffer,MIME_CT_ANNOTATE, AttributeStrn+aMacCreator,Long2Hex(scratch,creator), CrLf);
FSWriteP(stream->mailStream->refN,buffer);
ComposeRString(buffer,MIME_CD_FMT, InterestHeadStrn+hContentDisposition, ATTACHMENT, AttributeStrn+aFilename, name, CrLf);
FSWriteP(stream->mailStream->refN,buffer);
ComposeRString(buffer,MIME_V_FMT, InterestHeadStrn+hContentEncoding, IMAP_STUB_ENCODING, CrLf);
FSWriteP(stream->mailStream->refN,buffer);
FSWriteP(stream->mailStream->refN,CrLf);
// save the stub information to the message spool file
NumToString((*CurPersSafe)->persId, buffer);
PCat(buffer, "\p,");
FSWriteP(stream->mailStream->refN,buffer);
NumToString(uid, buffer);
PCat(buffer, "\p,");
FSWriteP(stream->mailStream->refN,buffer);
ComposeString(buffer, "\p%s,", section);
FSWriteP(stream->mailStream->refN,buffer);
NumToString(body->size.bytes, buffer);
PCat(buffer, "\p,");
FSWriteP(stream->mailStream->refN,buffer);
NumToString(body->size.lines, buffer);
PCat(buffer, "\p,");
FSWriteP(stream->mailStream->refN,buffer);;
FSWriteP(stream->mailStream->refN,CrLf);
}
return (*name != 0);
}
/************************************************************************
* XMacHeaders - search the body for x-mac-type and x-mac-creator
************************************************************************/
void XMacHeaders(IMAPBODY *body, ResType *type, ResType *creator)
{
Str255 macType, macCreator, scratch;
PARAMETER *param = nil;
// must have a body for this to make any sense
if (!body) return;
// will be looking for x-mac-type and x-mac-creator headers
GetRString(macType, AttributeStrn+aMacType);
GetRString(macCreator, AttributeStrn+aMacCreator);
// iterate through the body's parameter, looking for these headers
param = body->parameter;
while (param)
{
// x-mac-type?
if (!pstrincmp(param->attribute, macType,macType[0]))
{
if (noErr==Hex2Bytes(param->value,8,scratch)) BMD(scratch,type,sizeof(ResType));
}
// x-mac-creator?
else if (!pstrincmp(param->attribute, macCreator,macCreator[0]))
{
if (noErr==Hex2Bytes(param->value,8,scratch)) BMD(scratch,creator,sizeof(ResType));
}
// next parameter
param = param->next;
}
}
/************************************************************************
* IMAPError - alert the user that an IMAP operation has failed.
************************************************************************/
OSErr IMAPError(short operation, short explanation, OSErr err)
{
return (IE(nil, operation, explanation, err));
}
OSErr IE(IMAPStreamPtr imapStream, short operation, short explanation, OSErr err)
{
Str255 scratch;
Str255 operationText;
Str255 explanationText;
Str255 errText;
Str63 mboxName;
short realSettingsRef = SettingsRefN;
// if the user cancelled, don't display an error message
if (CommandPeriod) return (userCanceledErr);
// if we've already displayed an error, just return
if (imapStream && imapStream->mailStream && imapStream->mailStream->errorred)
{
imapStream->mailStream->errorred = false;
return (err);
}
SettingsRefN = GetMainGlobalSettingsRefN();
// describe what went wrong
// Stash mbox name, if any
if (imapStream && imapStream->mbox)
{
*mboxName = 1;
mboxName[1] = ' ';
PCopy(mboxName,(*imapStream->mbox)->mailboxSpec.name);
}
else
*mboxName = 0;
// special case error descriptions that want the name of the mailbox
if ((operation == kIMAPCompleteResync) && imapStream && imapStream->mbox)
{
ComposeString(explanationText, GetRString(scratch,IMAP_OPERATIONS_STRN+operation), (*imapStream->mbox)->mailboxSpec.name);
ComposeRString(operationText,IMAP_ERR_OPERATION_FMT,explanationText);
// have mbox name, do not append
*mboxName = 0;
}
else
ComposeRString(operationText,IMAP_ERR_OPERATION_FMT,GetRString(scratch,IMAP_OPERATIONS_STRN+operation));
// give an explanation, if none was provided from the server.
switch (err)
{
case errIMAPOutOfMemory:
case errIMAPNoServer:
case errIMAPNoMailstream:
case errIMAPNoAccount:
case errIMAPNoMailbox:
case errIMAPMailboxNameInvalid:
case errIMAPStreamIsLocked:
case errNotIMAPPers:
case errNotIMAPMailboxErr:
case errIMAPListInUse:
case errIMAPNoTrash:
case errIMAPStubFileBad:
case errIMAPBadEncodingErr:
case errIMAPMailboxChangedErr:
case errIMAPReadOnlyStreamErr:
GetRString(explanationText,IMAP_EXPLANATIONS_STRN+explanation);
break;
default:
// otherwise, use the error reported by the IMAP server
if (!gIMAPErrorString[0]) GetRString(explanationText,IMAP_EXPLANATIONS_STRN+explanation);
else
{
PCopy(explanationText, gIMAPErrorString);
gIMAPErrorString[0] = 0;
}
}
SettingsRefN = realSettingsRef;
// and the error number
NumToString(err, errText);
PSCat(errText,mboxName);
MyParamText(operationText,errText,explanationText,"");
ReallyDoAnAlert(BIG_OK_ALRT,Stop);
if (imapStream && imapStream->mailStream && imapStream->mailStream->errorred) imapStream->mailStream->errorred = false;
return (err);
}
/************************************************************************
* FTMExpunge - move all messages marked for deletion to the trash
* mailbox, and expunge
************************************************************************/
OSErr FTMExpunge(IMAPStreamPtr imapStream, TOCHandle tocH)
{
OSErr err = noErr;
short sumNum;
MailboxNodeHandle trash = GetIMAPTrashMailbox(CurPersSafe, false, false);
Handle uids = nil;
long messageCount = 0;
long count;
unsigned long uid;
Str255 uidStr;
// must have a toc to look at
if (!tocH)
return (noErr);
// nothing to do if we're caching deleted messages
if (GetHiddenCacheMailbox(TOCToMbox(tocH), false, false))
return (noErr);
// only makes sense to do this when FTM is on
if (FancyTrashForThisPers(tocH))
{
// must have a trash mailbox
if (trash != nil)
{
// must have a stream and be connected
if (imapStream && IsSelected(imapStream->mailStream))
{
//
// build the list of deleted messages
//
// how many deleted messages are there?
messageCount = CountDeletedIMAPMessages(tocH);
// continue if there are messages to be deleted
if (messageCount > 0)
{
// build a list of uids to move
uids = NuHandleClear(messageCount*sizeof(unsigned long));
if (uids)
{
// grab the messages in the visible tocH
count = messageCount;
for (sumNum=0;sumNum<(*tocH)->count && count;sumNum++)
if ((*tocH)->sums[sumNum].opts&OPT_DELETED)
BMD(&((*tocH)->sums[sumNum].uidHash),&((unsigned long *)(*uids))[--count],sizeof(unsigned long));
}
else
{
WarnUser(MemError(), MEM_ERR);
return (false);
}
//
// now COPY those messages to the trash mailbox
//
if (uids)
{
//unmark the messages for deletion
count = messageCount;
while (count--)
{
// mark the message for deletion. Do not expunge.
uid = ((unsigned long *)(*uids))[count];
sprintf (uidStr,"%lu",uid);
UIDUnDeleteMessages(imapStream, uidStr);
}
// copy the messages to the trash mailbox
CopyMessages(imapStream, trash, uids, true, true);
// and remark them for deletion
count = messageCount;
while (count--)
{
// mark the message for deletion. Do not expunge.
uid = ((unsigned long *)(*uids))[count];
sprintf (uidStr,"%lu",uid);
UIDDeleteMessages(imapStream, uidStr, false);
}
}
}
// else
// no messages to be deleted. Continue with no error.
//
// Now do the actual expunge
//
if (DoesIMAPMailboxNeed(TOCToMbox(tocH), kNeedsFilter))
{
// this mailbox is currently being filtered. Mark it for an expunge and do it later.
FlagForExpunge(tocH);
}
else
{
if (Expunge(imapStream))
UpdateLocalSummaries(tocH, uids, true, true, false);
// No longer need to consider this mailbox in auto expunge
SetIMAPMailboxNeeds(TOCToMbox(tocH), kNeedsAutoExp, false);
}
}
else
err = errIMAPNotConnected;
}
else
err = errIMAPNoTrash;
}
return (err);
}
/************************************************************************
* EmptyAllImapTrashes - zap all the IMAP trash mailboxes
************************************************************************/
Boolean EmptyImapTrashes(IMAPEmptyTrashEnum which)
{
Boolean result = false;
PersHandle oldPers = CurPers;
Boolean online = false;
// loop through all personalities and empty the trash.
for (CurPers = PersList; CurPers; CurPers = (*CurPers)->next)
{
// is this an IMAP personality?
if (IsIMAPPers(CurPers))
{
if ((which == kEmptyAllTrashes) // all trash mailboxes
|| ((which == kEmptyAutoCheckTrashes) && PrefIsSet(PREF_AUTO_CHECK))// only those belonging to personalities that check mail automatically
|| ((which == kEmptyActiveTrashes) && IMAPPersActive(CurPers))) // only "active" IMAP trash mailboxes
{
// is this personality currently using the fancy trash scheme?
if (!PrefIsSet(PREF_IMAP_NO_FANCY_TRASH))
{
// make sure we're online
if (!online)
{
if (Offline && GoOnline()) return(false);
else online = true;
}
result = IMAPEmptyPersTrash();
}
}
}
}
return (result);
}
/************************************************************************
* IMAPEmptyPersTrash - empty the trash for the current personality.
* Return if something got expunged.
************************************************************************/
Boolean IMAPEmptyPersTrash(void)
{
Boolean result = false;
MailboxNodeHandle persTrash = nil;
TOCHandle trashToc = nil;
// locate the trash mailbox
if ((persTrash=GetIMAPTrashMailbox(CurPers, false, true)))
{
LockMailboxNodeHandle(persTrash);
trashToc=TOCBySpec(&((*persTrash)->mailboxSpec));
UnlockMailboxNodeHandle(persTrash);
// remove all deleted messages from the trash mailbox
if (trashToc) result = IMAPWipeBox(trashToc, persTrash);
}
return (result);
}
/************************************************************************
* IMAPWipeBox - wipe out an entire mailbox on the server. Not
* thread safe.
************************************************************************/
Boolean IMAPWipeBox(TOCHandle tocToWipe, MailboxNodeHandle nodeToWipe)
{
Boolean result = false;
IMAPStreamPtr imapStream = nil;
short sum;
// must have a box to wipe
if (!tocToWipe || !nodeToWipe) return (false);
// first, go close all the open messages in this toc
for (sum=0;sum<(*tocToWipe)->count;sum++)
{
if ((*tocToWipe)->sums[sum].messH) CloseMyWindow(GetMyWindowWindowPtr((*(*tocToWipe)->sums[sum].messH)->win));
}
// Create a new IMAP stream
if (imapStream = GetIMAPConnection(IMAPDeleteTask, CAN_PROGRESS))
{
// SELECT the mailbox this message resides in on the server
LockMailboxNodeHandle(nodeToWipe);
if (IMAPOpenMailbox(imapStream, (*nodeToWipe)->mailboxName,false))
{
// mark all the messages for deletion and expunge
result = UIDDeleteMessages(imapStream, "1:*", true);
// No longer need to consider this mailbox in auto expunge
SetIMAPMailboxNeeds(nodeToWipe, kNeedsAutoExp, false);
if (!result) // delete failed
IMAPError(kIMAPEmptyTrash, kIMAPDeleteMessage, errIMAPDeleteMessage);
}
else // failed to open mailbox
IE(imapStream, kIMAPEmptyTrash, kIMAPSelectMailboxErr, errIMAPSelectMailbox);
UnlockMailboxNodeHandle(nodeToWipe);
// close down and clean up
PROGRESS_MESSAGER(kpSubTitle,CLEANUP_CONNECTION);
CleanupConnection(&imapStream);
}
// empty the trash mailbox if the delete succeeded. Don't care if it was open.
if (result)
{
// this is happening in the foreground, so go ahead an just wipe out all the summaries in the mailbox.
for(sum=(*tocToWipe)->count;sum--;) DeleteIMAPSum(tocToWipe,sum);
}
return (result);
}
/************************************************************************
* GetFilenameParameter - Search the Content-Disposition parameter list
* for a "filename" parameter. Returns a p-string.
************************************************************************/
void GetFilenameParameter(IMAPBODY *body, Str255 fileName)
{
Str255 mimeName;
Str255 mimeCDName;
PARAMETER *param = nil;
GetRString(mimeName, NAME); // "name"
GetRString(mimeCDName, AttributeStrn+aFilename); // "filename"
// Initialize in case we fail:
*fileName = 0;
// Must have a body:
if (!body) return;
// If there is a content-disposition, use that.
param = body->disposition.parameter;
while (param)
{
if (param->attribute && (strincmp(param->attribute, mimeCDName+1, mimeCDName[0]) == 0))
{
if (param->value)
{
fileName[0] = MIN(strlen(param->value), 255);
BMD(param->value, fileName+1, fileName[0]);
break;
}
}
param = param->next;
}
if (fileName[0] == nil)
{
// If there is a parameter list from content-type:, use the filename from there
param = body->parameter;
while (param)
{
if (param->attribute && (((strincmp(param->attribute, mimeName+1, mimeName[0]) == 0 ))
|| (strincmp(param->attribute, mimeCDName+1, mimeCDName[0]) == 0)))
{
if (param->value)
{
fileName[0] = MIN(strlen(param->value), 255);
BMD(param->value, fileName+1, fileName[0]);
break;
}
}
param = param->next;
}
}
if (fileName[0] == nil)
{
// if we can't find a filename at all, assign one.
GetRString(fileName, UNTITLED);
}
}
/************************************************************************
* BodyTypeCodeToPString - given an IMAPBODY body type, return a p-string
************************************************************************/
PStr BodyTypeCodeToPString(short type, PStr string)
{
short index = 0;
string[0] = 0;
switch (type)
{
case TYPETEXT:
index = MIME_TEXT;
break;
case TYPEIMAGE:
index = IMAGE;
break;
case TYPEAPPLICATION:
index = MIME_APPLICATION;
break;
case TYPEMESSAGE:
index = MIME_MESSAGE;
break;
case TYPEMULTIPART:
index = MIME_MULTIPART;
break;
}
if (index) GetRString(string, index);
return (string);
}
/************************************************************************
* IsIMAPAttachmentStub - return true if the spec points to an IMAP
* attachment stub.
************************************************************************/
Boolean IsIMAPAttachmentStub(FSSpecPtr spec)
{
Boolean isIMAPAttachment = false;
OSErr err = noErr;
Str255 scratch, parentName;
// figure out the name of the parent folder of this file
if ((err=GetDirName(nil,spec->vRefNum,spec->parID,parentName))==noErr)
{
// is its parent's name "IMAP Attachments"?
if (StringSame(parentName, GetRString(scratch, IMAP_ATTACH_FOLDER)))
{
// then this is an IMAP attachment.
isIMAPAttachment = true;
}
}
return (isIMAPAttachment);
}
/************************************************************************
* DownloadIMAPAttachment - given a spec pointing to a stub file,
* go download the message from the server.
************************************************************************/
unsigned long DownloadIMAPAttachment(FSSpecPtr attachSpec, MailboxNodeHandle mailbox, Boolean forceForeground)
{
unsigned long result = 0;
FSSpecHandle attach = nil;
OSErr err = noErr;
attach = NuHandle(sizeof(FSSpec));
if (attach && (err=MemError())==noErr)
{
BMD(attachSpec, *attach, sizeof(FSSpec));
result = DownloadIMAPAttachments(attach, mailbox, forceForeground);
}
else
{
WarnUser(MEM_ERR, err);
ZapHandle(attach);
}
return (result);
}
/************************************************************************
* DownloadIMAPAttachments - given a spec pointing to a stub file,
* go download the message from the server.
************************************************************************/
unsigned long DownloadIMAPAttachments(FSSpecHandle attachments, MailboxNodeHandle mailbox, Boolean forceForeground)
{
unsigned long uid = 0;
IMAPTransferRec imapInfo;
OSErr err = noErr;
PersHandle oldPers = CurPers;
XferFlags flags;
// make sure we where given something to download
if (!attachments) return (0);
// we must be online
if (Offline && GoOnline()) return(OFFLINE);
if (PrefIsSet(PREF_THREADING_OFF) || !ThreadsAvailable() || forceForeground)
{
// download the attachment
uid = DoDownloadIMAPAttachments(attachments, mailbox);
}
else
{
// figure out who this mailbox belongs to
CurPers = MailboxNodeToPers(mailbox);
if (CurPers)
{
// collect password now if we need it.
err = CheckIMAPSettingsForPers();
// if we were given a password, set up the thread
if (err==noErr)
{
Zero(flags);
Zero(imapInfo);
imapInfo.attachments = attachments;
imapInfo.targetBox = mailbox;
imapInfo.command = IMAPAttachmentFetch;
err = SetupXferMailThread (false, false, true, false, flags, &imapInfo);
// and remove any old task errors
RemoveTaskErrors(IMAPAttachmentFetch,(*CurPers)->persId);
// forget the keychain password so it's not written to the settings file
if (KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN)) Zero((*CurPers)->password);
}
}
CurPers = oldPers;
}
return (uid);
}
/************************************************************************
* DoDownloadIMAPAttachments - quit talking and start doing it.
************************************************************************/
unsigned long DoDownloadIMAPAttachments(FSSpecHandle attachments, MailboxNodeHandle mailbox)
{
OSErr err = noErr;
AttachmentStubStruct stub;
IMAPStreamPtr imapStream;
PersHandle oldPers = CurPers;
PersHandle pers = nil;
FSSpec stubSpec, attachSpool, attachSpec, exist, newExist;
short attachCount = 0;
short ref = -1;
unsigned long result = 0;
Str255 mName, progressMessage;
FInfo decodedInfo;
Boolean tweakInfo;
Boolean fetched = false;
UpdateNodeHandle update = nil;
TOCHandle tocH;
long sumNum;
// must have a stub folder, and a mailbox
if (!attachments || !mailbox) return (false);
// iterate through the list of stubs to download
for (attachCount = 0; (attachCount < (GetHandleSize_(attachments)/sizeof(FSSpec))) && !CommandPeriod; attachCount++)
{
stubSpec = (*attachments)[attachCount];
//
// read the info from the stub file
//
if ((err=GetStubInfo(&stubSpec, &stub))==noErr)
//
// Download the attachment into a spool file
//
if (pers=FindPersById(stub.persID))
{
CurPers = pers;
// display some progress indicating which mailbox we're downloading to?
LockMailboxNodeHandle(mailbox);
PathToMailboxName ((*mailbox)->mailboxName, mName, (*mailbox)->delimiter);
UnlockMailboxNodeHandle(mailbox);
ComposeRString(progressMessage,IMAP_FETCHING_MESSAGE,mName);
PROGRESS_START;
PROGRESS_MESSAGE(kpTitle,progressMessage);
PROGRESS_MESSAGER(kpSubTitle,IMAP_FETCH_ATTACHMENT);
// Create a new IMAP stream
imapStream = GetIMAPConnection(IMAPAttachmentFetch, CAN_PROGRESS);
if (imapStream)
{
// SELECT the mailbox the attachment resides in on the server
LockMailboxNodeHandle(mailbox);
if (IMAPOpenMailbox(imapStream, (*mailbox)->mailboxName,false))
{
// indicate what we're downloading
ComposeRString(progressMessage, IMAP_FETCH_ATTACHMENT_NAME, stubSpec.name);
PROGRESS_MESSAGE(kpMessage,progressMessage);
BYTE_PROGRESS(nil,0,stub.sizeBytes);
// put the attachment into a new file
SimpleMakeFSSpec(stubSpec.vRefNum, stubSpec.parID, stubSpec.name, &attachSpool);
if (UniqueSpec(&attachSpool,31)) continue;
// open the file
if ((err=HCreate(attachSpool.vRefNum,attachSpool.parID,attachSpool.name,CREATOR,'TEXT'))==noErr)
{
if ((err=AFSHOpen(attachSpool.name,attachSpool.vRefNum,attachSpool.parID,&ref,fsRdWrPerm))==noErr)
{
// kill any existing resources
FSpKillRFork(&attachSpool);
// Start writing at the beginning of the file
if ((err=SetEOF(ref,0))==noErr) err = SetFPos(ref, fsFromStart, 0);
// tell the imap stream where to put it
imapStream->mailStream->refN = ref;
// go download the part(s)
if (imapStream->mailStream->refN > 0)
{
// go download the part(s) to the spool file
if (!(fetched = DownloadAttachmentToSpoolFile(imapStream, nil, stub.uid, nil, stub.section)))
{
// failed to get the attachment.
err = IMAPError(kIMAPFetchAttachment, kIMAPAttachmentFetchErr, errIMAPCouldNotFetchPart);
}
}
}
else
{
err = IMAPError(kIMAPFetchAttachment, kIMAPTempFileExistErr, err);
}
}
else
{
err = IMAPError(kIMAPFetchAttachment, kIMAPTempFileExistErr, err);
}
}
else // failed to open the destination mailbox.
{
IE(imapStream, kIMAPFetchAttachment, kIMAPSelectMailboxErr, errIMAPSelectMailbox);
}
UnlockMailboxNodeHandle(mailbox);
// close down and clean up
PROGRESS_MESSAGER(kpSubTitle,CLEANUP_CONNECTION);
CleanupConnection(&imapStream);
PROGRESS_END;
// if there was an error, or we cancelled ...
if (err || CommandPeriod)
{
// failed - close and delete the spool file
FSClose(ref);
FSpDelete(&attachSpool);
fetched = false;
}
}
else
{
err = IMAPError(kIMAPFetchAttachment, kIMAPMemErr, errIMAPOutOfMemory);
}
CurPers = oldPers;
}
else
err = IMAPError(kIMAPFetchAttachment, kIMAPStubFileErr, errIMAPStubFileBad);
//
// decode the spool file into an attachment, and swap it with the stub file
//
if (fetched)
{
Boolean renamedExisting = false;
Str31 otherName;
// reset the progress indicator ...
BYTE_PROGRESS(nil, 0, stub.sizeLines);
PROGRESS_MESSAGER(kpSubTitle,IMAP_DECODING_ATTACHMENT);
if ((err == noErr) && SpoolFileToAttachment(mailbox, stub.uid, &attachSpool, &attachSpec))
{
// final progress message ...
BYTE_PROGRESS(nil, stub.sizeLines, stub.sizeLines);
PROGRESS_MESSAGER(kpSubTitle,CLEANUP_CONNECTION);
// get the decoded attachment's file info.
tweakInfo = (AFSpGetFInfo(&attachSpec,&attachSpec,&decodedInfo) == noErr);
// we're going to be moving files around. Figure out the next available name in the Attachment folder.
exist = AttFolderSpec;
PCopy(exist.name,stubSpec.name);
newExist = exist;
UniqueSpec(&newExist,31);
// swap file information with the stub file
if ((FSpExchangeFiles(&attachSpec, &stubSpec))==noErr)
{
// delete the stub file (now attachSpec)
FSpDelete(&attachSpec);
// move the attachment (now stubSpec) into the attachments folder
if ((err = SpecMove(&stubSpec,&AttFolderSpec))==dupFNErr)
{
// failed to move it. Rename conflicting file to the next available name
if (!FSpRename(&exist,newExist.name))
{
renamedExisting = true;
err = SpecMove(&stubSpec,&AttFolderSpec);
}
}
if (err == noErr)
{
// stubSpec was moved to the attachments folder ...
stubSpec.parID = AttFolderSpec.parID;
}
// give it it's real life decoded name.
// AttachSpec.name is guaranteed to be unique, since the decoder created it right into the attachments folder.
// only do this if the stub file name is a MacToOther() name of the decoded name, so AttLine2Spec() can find it later.
otherName[0] = nil;
Mac2OtherName(otherName, attachSpec.name);
// leave the encoded name if it's been uniquified. Otherwise, the message will lose track of it!
if (otherName[0]==stubSpec.name[0])
{
if (!strincmp(otherName+1,stubSpec.name+1,otherName[0]))
{
FSpRename(&stubSpec,attachSpec.name);
PCopy(stubSpec.name,attachSpec.name);
}
}
// and name the duplicate file back to it's old name
if (renamedExisting) FSpRename(&newExist, exist.name);
// make sure the finder finds out about this new file and it's type
if (tweakInfo) TweakFileType(&stubSpec,decodedInfo.fdType,decodedInfo.fdCreator);
}
// record for statistics
if (tocH = TOCBySpec(&(*mailbox)->mailboxSpec))
{
long sumNum;
if (!TOCFindMessByMID(stub.uid,tocH,&sumNum))
UpdateNumStatWithTime(kStatReceivedAttach,1,(*tocH)->sums[sumNum].seconds+ZoneSecs());
}
// update the FSSpecHandle with the location of the downloaded attachment. Need this for updates later.
(*attachments)[attachCount] = stubSpec;
// return the uid of the message we just fetched part of
result = stub.uid;
}
else
{
// Something went wrong. We may have to display an error.
// find the summary of the message who'se attachment we just fetched.
tocH = TOCBySpec(&(*mailbox)->mailboxSpec);
if (tocH && *tocH)
{
if (noErr==(TOCFindMessByMID(stub.uid,tocH,&sumNum)))
{
if (sumNum < (*tocH)->count)
{
if ((*tocH)->sums[sumNum].opts&OPT_EMSR_DELETE_REQUESTED)
{
// a translator asked to delete this message. Ignore any errors here, the attachment was probably processed.
}
else
{
// there was an error decoding the attachment.
IMAPError(kIMAPFetchAttachment, kIMAPAttachmentDecodeErr, BadEncoding || errIMAPBadEncodingErr);
// delete the attachspec as well
FSpDelete(&attachSpec);
}
}
}
}
// delete the spool file
FSClose(ref);
FSpDelete(&attachSpool);
}
}
}
// make the window aware of the new attachment
update = NewZH(UpdateNode);
if (update)
{
(*update)->mailboxSpec = (*mailbox)->mailboxSpec;
(*update)->uid = stub.uid;
(*update)->attachSpecs = attachments;
LL_Queue(gWindowUpdates, update, (UpdateNodeHandle));
}
return (result);
}
/************************************************************************
* GetStubInfo - retrieve the stub info from inside a stub file
************************************************************************/
OSErr GetStubInfo(FSSpecPtr spec, AttachmentStubPtr stub)
{
OSErr err = noErr;
UHandle stubH = nil;
unsigned long len = 0;
char *begin, *scan, *end;
Str255 buf;
const unsigned long bufSize = sizeof(Str255) - 1;
// clear out the stub
WriteZero(stub, sizeof(AttachmentStubStruct));
// read the stub info from the stub file
if ((err=Snarf(spec, &stubH, 0))==noErr)
{
// stubH contains a comma separated list. Parse through it.
len = GetHandleSize(stubH);
if (len)
{
LDRef(stubH);
end = *stubH + len;
// start at the beginning of the comma separated line
scan = begin = *stubH;
while ((*scan != ',') && (scan < end)) scan++;
len = scan - begin;
if ((scan < end) && (len <= bufSize))
{
// persID
*scan = 0;
strcpy(buf, begin);
stub->persID = atol(buf);
begin = scan + 1;
while ((*scan != ',') && (scan < end)) scan++;
len = scan - begin;
if ((scan < end) && (len <= bufSize))
{
// uid
*scan = 0;
strcpy(buf, begin);
stub->uid = atol(buf);
begin = scan + 1;
while ((*scan != ',') && (scan < end)) scan++;
len = scan - begin;
if ((scan < end) && (len <= bufSize))
{
// section string
*scan = 0;
strcpy(stub->section, begin);
begin = scan + 1;
while ((*scan != ',') && (scan < end)) scan++;
len = scan - begin;
if ((scan < end) && (len <= bufSize))
{
// sizeBytes
*scan = 0;
strcpy(buf, begin);
stub->sizeBytes = atol(buf);
begin = scan + 1;
while ((*scan != ',') && (scan < end)) scan++;
len = scan - begin;
if ((scan < end) && (len <= bufSize))
{
// sizeLines
*scan = 0;
strcpy(buf, begin);
stub->sizeLines = atol(buf);
}
}
}
}
}
UL(stubH);
}
// Cleanup
ZapHandle(stubH);
}
return (err);
}
/************************************************************************
* PETEHandleToMailboxNode - turn a PETEHandle into a MailboxNode
************************************************************************/
MailboxNodeHandle PETEHandleToMailboxNode(PETEHandle pte)
{
MyWindowPtr win = nil;
TOCHandle tocH = nil;
MailboxNodeHandle node = nil;
short kind = 0;
TOCHandle realTOC;
short realSumNum;
if (win = (*PeteExtra(pte))->win)
{
kind = GetWindowKind(GetMyWindowWindowPtr(win));
if (kind == MESS_WIN) tocH = (*Win2MessH(win))->tocH;
else if (kind == MBOX_WIN) tocH = (TOCHandle)GetMyWindowPrivateData(win);
// get the real TOC in case this message is in a search window.
realTOC = GetRealTOC(tocH,0,&realSumNum);
if (realTOC)
{
node = TOCToMbox(realTOC);
}
}
return (node);
}
/************************************************************************
* FetchIMAPAttachment - Given a spec and a pte, download the attachment.
* Also update the spec paramter to point to the downloaded attachment.
************************************************************************/
OSErr FetchIMAPAttachment(PETEHandle pte, FSSpecPtr spec, Boolean forceForeground)
{
OSErr err = noErr;
AttachmentStubStruct stub;
TOCHandle mailboxTocH = nil;
long sumNum;
MailboxNodeHandle mailbox = PETEHandleToMailboxNode(pte);
long fileId;
CInfoPBRec hfi;
// must have a stub file spec ...
if (!spec || !IsIMAPAttachmentStub(spec)) return (paramErr);
// must have a valid IMAP mailbox ...
if (!mailbox) return (errNotIMAPMailboxErr);
// read the attachment info from the stub file
if ((err=GetStubInfo(spec, &stub))==noErr)
{
// which toc are we dealing with?
LockMailboxNodeHandle(mailbox);
mailboxTocH = FindTOC(&(*mailbox)->mailboxSpec);
UnlockMailboxNodeHandle(mailbox);
if (mailboxTocH)
{
// which message owns this attachment?
if ((err=TOCFindMessByMID(stub.uid, mailboxTocH, &sumNum))==noErr)
{
// remember where the stub file lives
err = HGetCatInfo(spec->vRefNum, spec->parID, spec->name, &hfi);
fileId = hfi.hFileInfo.ioDirID;
// download the stub
if ((err==noErr) && !DownloadIMAPAttachment(spec, mailbox, true)) err = errIMAPCouldNotFetchPart;
// Update the spec to point ot the downloaded attachment
if (err == noErr)
{
err = FSResolveFID(spec->vRefNum, fileId, spec);
}
}
}
}
return (err);
}
/************************************************************************
* DownloadAttachmentToSpoolFile - download a given section to a spool
* file, performing no decoding. Special case for AppleDouble.
************************************************************************/
Boolean DownloadAttachmentToSpoolFile(IMAPStreamPtr imapStream, IMAPBODY *parentBody, unsigned long uid, char *parentSection, char *sectionToFetch)
{
Str255 pSection, section, previousSection, boundarySection;
IMAPBODY *previousBody = nil;
Boolean result = false;
Boolean topLevel = true;
PART *part = nil;
IMAPBODY *body = nil;
Str255 scratch;
short partNum = 0;
Str255 attachment, appledouble;
Boolean fetched = false;
// must have a section to download
if (!sectionToFetch || !*sectionToFetch) return (false);
// Make sure we have an imap stream and we've SELECTed a mailbox.
if (!imapStream || !IsSelected(imapStream->mailStream)) return (false);
// special case if we're fetching an Appledoubled attachment
GetRString(appledouble,MIME_APPLEDOUBLE);
// figure out if this is the top level or not
if (parentSection == nil) topLevel = true;
else if (*parentSection == nil) topLevel = true;
else topLevel = false;
// first get the message body
if (!parentBody) parentBody = UIDFetchStructure(imapStream, uid);
// now locate the section we want to download
if (parentBody)
{
// Fetch and save the header
if (!topLevel || (result=UIDFetchHeader(imapStream, uid, true)))
{
// Loop through all parts:
part = parentBody->nested.part;
while (part && !fetched)
{
// save the previous body and section
strcpy(previousSection, section);
previousBody = body;
body = &(part->body);
// Is this the first part??
if (topLevel)
{
NumToString(++partNum,pSection); // no dot
PtoCcpy(section, pSection);
}
else
{
// Copy parent section first. MUST have a non-NULL parent section if not top level!
ComposeString(pSection, "\p%s.%d", parentSection, ++partNum);
PtoCcpy(section, pSection);
}
// if this is an attachment ...
PtoCcpy(attachment,GetRString(scratch, ATTACH));
if (body->disposition.type && !striscmp(attachment, body->disposition.type))
{
// and it's the section we're looking for
if (!strcmp(sectionToFetch, section))
{
// turn on progress
imapStream->mailStream->showProgress = !IMAPFilteringUnderway();
// grab the previous part first if we're doing AppleDouble.
if (!pstrincmp(parentBody->subtype, appledouble,appledouble[0]))
{
// Some versions of OE don't properly label the appledouble section as an attachment.
// Make sure we don't write the boundary twice
if (striscmp(boundarySection, previousSection)) IMAPWriteBoundary(imapStream, parentBody, uid, previousSection, false);
result = AppendBodyTextToSpoolFile(imapStream, uid, previousSection, previousBody->size.bytes);
}
// grab the part.
IMAPWriteBoundary(imapStream, parentBody, uid, section, false);
result = AppendBodyTextToSpoolFile(imapStream, uid, section, body->size.bytes);
// done with progress ...
imapStream->mailStream->showProgress = false;
// we got it.
if (result) fetched = true;
}
}
else
{
strcpy(boundarySection, section);
IMAPWriteBoundary(imapStream, parentBody, uid, section, false);
}
// if this is a multipart part, and we haven't fetched the section yet, we may have to recurse.
if (!fetched && (body->type == TYPEMULTIPART)) fetched = DownloadAttachmentToSpoolFile(imapStream, body, uid, section, sectionToFetch);
// Next part.
part = part->next;
}
// write outer boundary
IMAPWriteBoundary(imapStream, parentBody, uid, nil, true);
}
}
return (fetched);
}
/************************************************************************
* SpoolFileToAttachment - convert a raw downloaded IMAP messages into
* an attachment. Delete the spool file when done.
* This is absolutely, positively, not thread safe.
************************************************************************/
Boolean SpoolFileToAttachment(MailboxNodeHandle mailbox, unsigned long uid, FSSpecPtr spoolSpec, FSSpecPtr attachSpec)
{
TransVector saveCurTrans = CurTrans;
short saveRefN=CurResFile();
static TransVector IMAPTrans = {nil,nil,nil,nil,nil,nil,nil,nil,nil,IMAPRecvLine,nil};
TOCHandle toTocH = nil;
unsigned long messNum = 0;
HeaderDHandle hdh=NewHeaderDesc(nil);
short lastHeaderTokenType;
Str255 buf;
short scratchRefN = -1;
FSSpec scratchSpec;
Boolean progressed = false;
long ticks = TickCount();
static Boolean inUse = false;
if (!hdh)
{
IMAPError(kIMAPFetchAttachment, kIMAPMemErr, MemError());
return (false);
}
// the decoder routines aren't currently threadsafe. The quick and dirty thing to do
// is to only allow one attachment decode at a time.
while (inUse && !CommandPeriod)
{
// put up a progress message if we've waiting for more than a second ...
if (!progressed && ((TickCount() - ticks)>60))
{
PROGRESS_MESSAGER(kpMessage,IMAP_WAITING_FOR_DECODER);
progressed = true;
}
CycleBalls();
if (MyYieldToAnyThread()) break;
}
inUse = true;
// must have a spool file of some length
if ((gIMAPMsgEnd = FSpDFSize(spoolSpec)) <= 0)
{
inUse = false;
return (false);
}
// must also have a scratch file
if ((scratchRefN = NewScratchFile(spoolSpec, &scratchSpec))==-1)
{
inUse = false;
return (false);
}
// clear out the attachment's spec's name
attachSpec->name[0] = 0;
// convert the uid to a messNum
LockMailboxNodeHandle(mailbox);
toTocH = TOCBySpec(&(*mailbox)->mailboxSpec);
UnlockMailboxNodeHandle(mailbox);
if (!toTocH)
{
inUse = false;
return (false);
}
for (messNum = 0; (messNum < (*toTocH)->count) && (uid!=(*toTocH)->sums[messNum].uidHash); messNum++);
/*
* allocate the lineio pointer
*/
if (!Lip) Lip = NuPtrClear(sizeof(*Lip));
if (!Lip)
{
(WarnUser(MEM_ERR,MemError()));
goto msgDone;
}
if (FSpOpenLine(spoolSpec,fsRdWrPerm,Lip))
goto msgDone;
// grab the message from the spool file
CurTrans = IMAPTrans;
BadBinHex = False;
BadEncoding = 0;
if (!AttachedFiles) AttachedFiles=NuHandle(0);
SetHandleBig_(AttachedFiles,0);
SeekLine(0,Lip);
lastHeaderTokenType = ReadHeader(nil,hdh,0,scratchRefN,False);
if (lastHeaderTokenType==EndOfHeader)
{
NoAttachments = false;
ReadEitherBody(nil,scratchRefN,hdh,buf,sizeof(buf),0,EMSF_ON_ARRIVAL);
// did we decode an attachment? Our scratch file will contain the information we need.
FindFirstAttachmentInSpec(&scratchSpec, attachSpec);
SetHandleBig_(AttachedFiles,0);
SaveAbomination(nil,0);
}
CurTrans = saveCurTrans;
msgDone:
if (Lip && Lip->refN)
{
CloseLine(Lip);
DisposePtr((Ptr)Lip);
Lip = nil;
}
// we're done with decoding.
inUse = false;
// Don't need spool file anymore
FSpDelete(spoolSpec);
// Nor do we need the scratch file
DeleteScratchFile(scratchRefN, &scratchSpec);
UseResFile(saveRefN);
return ((attachSpec->name[0] != 0) && !BadBinHex && (BadEncoding==0));
}
/************************************************************************
* IMAPRecvLine - read a line at a time from the spool file. Returns ".\015"
* at the ends of messages.
************************************************************************/
static OSErr IMAPRecvLine(TransStream stream, UPtr buffer, long *size)
{
#pragma unused(stream)
static Boolean wasFrom;
static Boolean wasNl=True;
short lineType;
if (!buffer) {Boolean retVal = wasFrom; wasFrom = False; return(retVal);}
wasFrom=False;
(*size)--;
lineType = GetLine(buffer,*size,size,Lip);
if (*buffer=='\012')
{
// remove linefeed char
BMD(buffer+1,buffer,*size-1);
(*size)--;
buffer[*size] = 0;
}
if (!*size || !lineType || wasNl&&(wasFrom=IsFromLine(buffer)) || TellLine(Lip)>=gIMAPMsgEnd)
{
// signal end-of-message
*size = 2;
buffer[0]='.'; buffer[1]='\015'; buffer[2]=0;
}
else if (lineType && wasNl && *buffer=='.')
{
// insert '.' at beginning of line
BMD(buffer,buffer+1,*size);
(*size)++;
*buffer = '.';
buffer[*size] = 0;
}
wasNl = !lineType || buffer[*size-1]=='\015';
return(noErr);
}
/************************************************************************
* NewScratchFile - create a scratch file
************************************************************************/
short NewScratchFile(FSSpecPtr where, FSSpecPtr scratchFile)
{
short ref = -1;
SimpleMakeFSSpec(where->vRefNum, where->parID, where->name, scratchFile);
if (UniqueSpec(scratchFile,31)==noErr)
{
if (HCreate(scratchFile->vRefNum,scratchFile->parID,scratchFile->name,CREATOR,'TEXT')==noErr)
{
if (AFSHOpen(scratchFile->name,scratchFile->vRefNum,scratchFile->parID,&ref,fsRdWrPerm)!=noErr)
{
ref = -1;
FSpDelete(scratchFile);
}
}
}
return (ref);
}
/************************************************************************
* NewScratchFile - create a scratch file
************************************************************************/
OSErr DeleteScratchFile(short ref, FSSpecPtr scratchFile)
{
FSClose(ref);
return (FSpDelete(scratchFile));
}
/************************************************************************
* FindFirstAttachmentInSpec - find the first attachment converted line
* in a file
************************************************************************/
void FindFirstAttachmentInSpec(FSSpecPtr inSpec, FSSpecPtr attachSpec)
{
OSErr err = noErr;
UHandle text = nil;
Zero(*attachSpec);
if ((err=Snarf(inSpec, &text, 0))==noErr)
{
if (text) FindAnAttachment(text,0,attachSpec,true,nil,nil,nil);
ZapHandle(text);
}
}
/************************************************************************
* CanFetchAttachment - return true if this attachment can be fetched.
************************************************************************/
Boolean CanFetchAttachment(FSSpecPtr spec)
{
Boolean result = false;
threadDataHandle index;
short ascan;
SignedByte state;
if (spec && spec->name[0])
{
if (IsIMAPAttachmentStub(spec))
{
result = true;
// are there any threads currently operating on this stub?
for (index=gThreadData;index && result;index=(*index)->next)
{
state = HGetState((Handle)index);
LDRef(index);
// scan through this thread's attachments specs
if ((*index)->imapInfo.attachments)
{
LDRef((*index)->imapInfo.attachments);
for (ascan = 0; result && ascan < (GetHandleSize((*index)->imapInfo.attachments)/sizeof(FSSpec)); ascan++)
{
if (SameSpec(spec, &(*((*index)->imapInfo.attachments))[ascan])) result = false;
}
UL((*index)->imapInfo.attachments);
}
HSetState((Handle)index,state);
}
}
}
return (result);
}
/************************************************************************
* UpdateIMAPWindows - scan through the gWindowUpdates and update
* windows as needed.
************************************************************************/
void UpdateIMAPWindows(void)
{
UpdateNodeHandle node = gWindowUpdates;
UpdateNodeHandle next;
TOCHandle tocH = nil;
long sumNum;
MyWindowPtr win = nil;
MessHandle messH = nil;
WindowPtr winWP;
TOCHandle searchTocH;
short searchSum;
// go through the list of nodes ...
while (node)
{
next = (*node)->next;
// remove the node from the list
LL_Remove(gWindowUpdates, node, (UpdateNodeHandle));
// Which mailbox does this message live in?
LDRef(node);
tocH = TOCBySpec(&(*node)->mailboxSpec);
if (tocH) // the toc is still open ...
{
// which message needs to be updated?
if (TOCFindMessByMID((*node)->uid,tocH,&sumNum) == noErr)
{
// update the message's summary.
if (HasStubFileAttachment(tocH, sumNum)) (*tocH)->sums[sumNum].opts |= OPT_FETCH_ATTACHMENTS;
else (*tocH)->sums[sumNum].opts &= ~OPT_FETCH_ATTACHMENTS;
// redraw the server column
InvalTocBox(tocH,sumNum,blServer);
// is there a message handle associated with this message?
if (messH = (*tocH)->sums[sumNum].messH)
{
// is the message open?
if (win = (*messH)->win)
{
// is the message visible?
if (IsWindowVisible(GetMyWindowWindowPtr(win)))
{
// then refresh the message
if (!RedoIMAPAttachmentIcons(win, nil, (*node)->attachSpecs))
{
// reopen the window if there was no attachment info, or if we failed.
RedisplayIMAPMessage(win);
}
}
}
}
// If this message is being previewed, cause it to be redisplayed
if ((*tocH)->previewID==(*tocH)->sums[sumNum].serialNum)
{
if (!RedoIMAPAttachmentIcons(nil, (*tocH)->previewPTE, (*node)->attachSpecs))
(*tocH)->previewID = 0;
}
// if this message is being previewed in a search window, cause it to be redisplayed.
for (winWP=FrontWindow_();winWP;winWP=GetNextWindow(winWP))
{
// consider all open search windows ...
if (IsSearchWindow(winWP))
{
GetSearchTOC(GetWindowMyWindowPtr(winWP),&searchTocH);
{
// is this search window previewing a message?
if ((*searchTocH)->previewID!=0)
{
// see if it's the same message as the one we're updating
searchSum = FindSumBySerialNum(searchTocH, (*searchTocH)->previewID);
if (searchSum < (*searchTocH)->count)
{
if ((*tocH)->sums[sumNum].uidHash == (*searchTocH)->sums[searchSum].uidHash)
{
// redisplay it
if (!RedoIMAPAttachmentIcons(nil, (*searchTocH)->previewPTE, (*node)->attachSpecs))
(*searchTocH)->previewID = 0;
}
}
}
}
}
}
}
}
UL(node);
// Cleanup
ZapHandle((*node)->attachSpecs);
ZapHandle(node);
// move on to the next node in the list
node = next;
}
}
/************************************************************************
* RedoIMAPAttachmentIcons - make new graphics for the specified
* imap attachments
************************************************************************/
Boolean RedoIMAPAttachmentIcons(MyWindowPtr win, PETEHandle previewPte, FSSpecHandle attachSpecs)
{
WindowPtr winWP = nil;
short attachCount;
Handle textH;
UPtr begin, end; /* beginning and end of all text */
UPtr lBegin, lEnd; /* beginning and end of current line */
Str255 line;
Byte state;
Boolean found = false;
FSSpec spec, curAtt;
short i;
long offset;
PETEHandle pte = previewPte;
// must have a window or a pte to update
if (!win && !previewPte) return (false);
// update the window if the window was passed in
if (win)
{
winWP = GetMyWindowWindowPtr(win);
pte = win->pte;
}
// must have some attachments to update
if (!attachSpecs || !*attachSpecs) return(false);
attachCount = GetHandleSize_(attachSpecs)/sizeof(FSSpec);
if (!attachCount) return (false);
// must have a valid window
if (!PeteIsValid(pte)) return(false);
if (PeteGetTextAndSelection(pte,&textH,nil,nil)) return(False);
if (!textH || !GetHandleSize(textH)) return(false);
state = HGetState(textH);
// set initial values
begin = LDRef(textH);
end = begin + GetHandleSize(textH);
// examine each line
for (lBegin=begin;lBegin<end;lBegin=lEnd+1)
{
for (lEnd=lBegin;lEnd<end && lEnd[0]!='\015';lEnd++);
MakePStr(line,lBegin,lEnd-lBegin);
if (!AttLine2Spec(line,&spec,false))
{
offset = lBegin-begin;
for (i = 0; i < attachCount; i++)
{
curAtt = (*attachSpecs)[i];
if (SameSpec(&curAtt, &spec))
{
if (FileGraphicChangeGraphic(pte, offset,&curAtt)==noErr)
found = true;
}
}
}
}
// rezoom the window
if (winWP && found && PrefIsSet(PREF_ZOOM_OPEN))
ReZoomMyWindow(winWP);
HSetState(textH,state);
return (found);
}
/************************************************************************
* RedisplayIMAPMessage - Redisplay an IMAP message that has just come in
************************************************************************/
void RedisplayIMAPMessage(MyWindowPtr win)
{
WindowPtr winWP = GetMyWindowWindowPtr(win);
ControlHandle blah;
MessHandle messH;
PETETextStyle style;
Rect rState;
SetPort_(GetWindowPort(winWP));
rState = CurState(winWP); // Make sure we rezoom correctly
SetWindowStandardState(winWP,&rState);
// Enable, but don't show, the GetGraphics control,
// in case this message has downloadable images
blah = FindControlByRefCon(win,mcGetGraphics);
if (blah) HiliteControl(blah,0);
// Redisplay the message
if (ReopenMessage(win)!=nil)
{
// properly enable the message icon buttons
messH = (MessHandle)GetMyWindowPrivateData(win);
if (messH) MessIBarUpdate(messH);
EnableMsgButtons(win,true);
// Rezoom the window
if (PrefIsSet(PREF_ZOOM_OPEN)) ReZoomMyWindow(winWP);
else
{
// fake the window out so it redraws itself.
Rect rPort;
MyWindowDidResize(win,nil);
InvalWindowRect(winWP,GetPortBounds(GetWindowPort(winWP),&rPort));
UpdateMyWindow(winWP);
}
// don't allow changes to the body yet
style.tsLock = peModLock;
PETESetTextStyle(PETE,TheBody,0L,0x7fffffff,&style,peLockValid);
PETEMarkDocDirty(PETE,TheBody,False);
win->isDirty = False;
}
}
/**********************************************************************
* FetchAllIMAPAttachments - go get the IMAP attachments for a message
**********************************************************************/
Boolean FetchAllIMAPAttachments(TOCHandle tocH, short sumNum, Boolean forceForeground)
{
Boolean result = true;
MyWindowPtr win = nil;
WindowPtr theWindow;
long offset;
Handle text;
FSSpec attachSpec;
MailboxNodeHandle mailbox;
PersHandle pers;
FSSpecHandle attachments = nil;
short numSpecs = 0;
OSErr err = noErr;
MessHandle messH = nil;
Boolean openedMessage = false;
// must have a tocH
if (!tocH) return (false);
// the toc must refer to an IMAP mailbox
mailbox = TOCToMbox(tocH);
pers = TOCToPers(tocH);
if (pers && mailbox)
{
// if the message is already open, nothing to do.
if (messH = (*tocH)->sums[sumNum].messH)
{
win = (*messH)->win;
UsingWindow(GetMyWindowWindowPtr(win));
}
// if the message is not open, open it.
if (win == nil)
{
win = GetAMessage(tocH,sumNum,nil,nil,false);
openedMessage = true;
}
if (win)
{
theWindow = GetMyWindowWindowPtr (win);
CacheMessage(tocH,sumNum);
if (!(text=(*tocH)->sums[sumNum].cache)) return false;
HNoPurge(text);
offset = (*tocH)->sums[sumNum].bodyOffset-1;
// scan message for attachments
while ((0<=(offset = FindAnAttachment(text,offset+1,&attachSpec,true,nil,nil,nil))))
{
// did we find an attachment stub?
if (IsIMAPAttachmentStub(&attachSpec))
{
// can we download it?
if (CanFetchAttachment(&attachSpec))
{
// add it to the list of attachments to fetch
if (attachments == nil)
{
attachments = NuHandle(sizeof(FSSpec));
numSpecs = 1;
}
else
{
numSpecs++;
SetHandleSize(attachments, numSpecs*sizeof(FSSpec));
}
if (!attachments || (err=MemError()))
{
WarnUser(MEM_ERR, err);
ZapHandle(attachments);
break;
}
else
{
BMD(&attachSpec, &(*attachments)[numSpecs-1], sizeof(FSSpec));
}
}
}
}
// now fetch the attachments
if (attachments) result = (DownloadIMAPAttachments(attachments, mailbox, forceForeground) > 0);
// cleanup
if (openedMessage && !IsWindowVisible (theWindow))
CloseMyWindow(theWindow);
else
NotUsingWindow(theWindow);
}
}
return (result);
}
/**********************************************************************
* FetchAllIMAPAttachmentsBySpec - given a spec, fetch all the IMAP
* attachments in the message that has this spec as a stub
**********************************************************************/
Boolean FetchAllIMAPAttachmentsBySpec(FSSpecPtr spec, MailboxNodeHandle mailbox, Boolean forceForeground)
{
Boolean result = false;
OSErr err = noErr;
AttachmentStubStruct stub;
TOCHandle mailboxTocH = nil;
long sumNum;
// must have a stub file spec ...
if (!spec || !IsIMAPAttachmentStub(spec)) return false;
// must have a mailbox
if (!mailbox) return (false);
// read the attachment info from the stub file
if ((err=GetStubInfo(spec, &stub))==noErr)
{
// which toc are we dealing with?
LockMailboxNodeHandle(mailbox);
mailboxTocH = FindTOC(&(*mailbox)->mailboxSpec);
UnlockMailboxNodeHandle(mailbox);
if (mailboxTocH)
{
// which message owns this attachment?
if ((err=TOCFindMessByMID(stub.uid, mailboxTocH, &sumNum))==noErr)
{
result = FetchAllIMAPAttachments(mailboxTocH, sumNum, forceForeground);
}
}
}
return (result);
}
/**********************************************************************
* HasStubFileAttachment - return true if this message has one or more
* stub files as attachments
**********************************************************************/
Boolean HasStubFileAttachment(TOCHandle tocH, short sumNum)
{
Boolean result = false;
MailboxNodeHandle mailbox;
PersHandle pers;
MessHandle messH = nil;
MyWindowPtr win = nil;
WindowPtr winWP;
Boolean openedMessage = false;
Handle text;
long offset;
FSSpec attachSpec;
// must have a tocH
if (!tocH) return (false);
// the toc must refer to an IMAP mailbox
mailbox = TOCToMbox(tocH);
pers = TOCToPers(tocH);
if (pers && mailbox)
{
// if the message is already open, nothing to do.
if (messH = (*tocH)->sums[sumNum].messH)
{
win = (*messH)->win;
UsingWindow(GetMyWindowWindowPtr(win));
}
// if the message is not open, open it.
if (win == nil)
{
win = GetAMessage(tocH,sumNum,nil,nil,false);
openedMessage = true;
}
if (win)
{
winWP = GetMyWindowWindowPtr (win);
CacheMessage(tocH,sumNum);
if (!(text=(*tocH)->sums[sumNum].cache)) return false;
HNoPurge(text);
offset = (*tocH)->sums[sumNum].bodyOffset-1;
// scan message for attachments
while (0<=(offset = FindAnAttachment(text,offset+1,&attachSpec,true,nil,nil,nil)))
{
// did we find an attachment stub?
if (IsIMAPAttachmentStub(&attachSpec))
{
// then one of the attachments is a stub file.
result = true;
break;
}
}
// cleanup
if (openedMessage && !IsWindowVisible (winWP))
CloseMyWindow(winWP);
else
NotUsingWindow(winWP);
}
}
return (result);
}
/**********************************************************************
* IMAPSearch - Given a list of mailboxes and search criteria, perform
* the search. This may involve downloading summaries.
**********************************************************************/
Boolean IMAPSearch(TOCHandle searchWin, BoxCountHandle boxesToSearch, IMAPSCHandle searchCriteria, Boolean matchAll)
{
OSErr err = noErr;
PersHandle pers, mboxPers;
MailboxNodeHandle mailbox;
short i, menuId, numBoxes = GetHandleSize_(boxesToSearch)/sizeof(BoxCountElem), numBoxesToSearch;
FSSpec spec;
Handle toSearch = nil;
DeliveryNodeHandle node = nil;
short numStarted = 0; // number of searches that were actually started
// must have some mailboxes to search
if (!boxesToSearch || !*boxesToSearch) return (false);
// must thave some search criteria
if (!searchCriteria || !*searchCriteria) return (false);
// set up the deliveryNode that the results will be returned in
// is there one already set up?
if (node = FindNodeByToc(searchWin)) return (false);
// Search each personality as needed.
for (pers = PersList; pers && (err==noErr); pers = (*pers)->next)
{
// create a handle to store the indices into boxesToSearch
if (!(toSearch = NuHandle(0)) || (err = MemError()))
{
WarnUser(MEM_ERR,err);
return (false);
}
for (i = 0; i < numBoxes; i++)
{
spec.parID = (*boxesToSearch)[i].dirId;
spec.vRefNum = (*boxesToSearch)[i].vRef;
menuId = (spec.parID==MailRoot.dirId && SameVRef(spec.vRefNum,MailRoot.vRef)) ? MAILBOX_MENU : FindDirLevel(spec.vRefNum,spec.parID);
MailboxMenuFile(menuId,(*boxesToSearch)[i].item,spec.name);
// is this an IMAP mailbox?
if (IsIMAPCacheFolder(&spec))
{
// then look at the mailbox cache file itself.
spec.parID = SpecDirId(&spec);
// does this mailbox belong to the current personality?
LocateNodeBySpecInAllPersTrees(&spec, &mailbox, &mboxPers);
if (mailbox && (mboxPers == pers))
{
// then add it to the list of mailboxes to be searched
numBoxesToSearch = (GetHandleSize(toSearch)/sizeof(short)) + 1;
SetHandleSize(toSearch, numBoxesToSearch * sizeof(short));
if (err=MemError())
{
WarnUser(MEM_ERR,err);
ZapHandle(toSearch);
return (false);
}
BMD(&i, &((short *)(*toSearch))[numBoxesToSearch-1], sizeof(short));
}
}
}
// were any mailboxes belonging to this personality supposed to be searched?
if (toSearch && (GetHandleSize_(toSearch) > 0))
{
// create a delivery node
if (node == NULL)
{
node = NewDeliveryNode(searchWin);
if (node)
{
(*node)->filter = false;
// queue the delivery node
QueueDeliveryNode(node);
}
else
{
WarnUser(MEM_ERR,MemError());
return (false);
}
}
// actually do the search
if (IMAPSearchServer(searchWin, pers, boxesToSearch, toSearch, searchCriteria, matchAll, 0, (numStarted != 0)))
numStarted++;
else
break;
}
else
{
ZapHandle(toSearch);
}
}
// Cleanup
ZapHandle(searchCriteria);
searchCriteria = nil;
// did we fail to start a search?
if (numStarted == 0)
{
// then clean up
DequeueDeliveryNode(node);
ZapHandle(node);
}
// return true if at least one search was started
return (numStarted != 0);
}
/**********************************************************************
* IMAPSearch - Given a mailbox and search criteria, perform the search.
* Start the search from the uid where the caller asks.
**********************************************************************/
Boolean IMAPSearchMailbox(TOCHandle searchWin, BoxCountHandle allBoxes, MailboxNodeHandle boxToSearch, IMAPSCHandle searchCriteria, Boolean matchAll, long firstUID)
{
OSErr err = noErr;
Handle toSearch = nil;
DeliveryNodeHandle node = nil;
short i, numBoxes;
short menuId;
FSSpec spec;
// must have a mailbox to search
if (!boxToSearch || !*boxToSearch) return (false);
// must thave some search criteria
if (!searchCriteria || !*searchCriteria) return (false);
// set up the deliveryNode that the results will be returned in
// is there one already set up?
if (node = FindNodeByToc(searchWin)) return (false);
node = NewDeliveryNode(searchWin);
if (node)
{
(*node)->filter = false;
// grumble grumble
numBoxes = GetHandleSize_(allBoxes)/sizeof(BoxCountElem);
for (i = 0; i < numBoxes; i++)
{
spec.parID = (*allBoxes)[i].dirId;
spec.vRefNum = (*allBoxes)[i].vRef;
menuId = (spec.parID==MailRoot.dirId && SameVRef(spec.vRefNum,MailRoot.vRef)) ? MAILBOX_MENU : FindDirLevel(spec.vRefNum,spec.parID);
MailboxMenuFile(menuId,(*allBoxes)[i].item,spec.name);
spec.parID = SpecDirId(&spec);
if (((*boxToSearch)->mailboxSpec.vRefNum == spec.vRefNum)
&& ((*boxToSearch)->mailboxSpec.parID == spec.parID))
{
toSearch = NuHandle(sizeof(short));
if (toSearch)
{
*((short *)(*toSearch)) = i;
break;
}
}
}
if (toSearch)
{
// queue the delivery node.
QueueDeliveryNode(node);
// then do the search
IMAPSearchServer(searchWin, FindPersById((*boxToSearch)->persId), allBoxes, toSearch, searchCriteria, matchAll, firstUID, false);
}
else ZapHandle(node);
}
else WarnUser(MEM_ERR,MemError());
// Cleanup
ZapHandle(searchCriteria);
searchCriteria = nil;
return ((toSearch != nil));
}
/**********************************************************************
* IMAPSearchServer - do a search on a set of mailboxes on the same
* server.
**********************************************************************/
Boolean IMAPSearchServer(TOCHandle searchWin, PersHandle pers, BoxCountHandle boxesToSearch, Handle toSearch, IMAPSCHandle searchCriteria, Boolean matchAll, long firstUID, Boolean bAlreadyOnline)
{
MailboxNodeHandle mailboxNode = nil;
OSErr err = noErr;
XferFlags flags;
IMAPTransferRec imapInfo;
Boolean result = false;
PersHandle oldPers = CurPers;
DeliveryNodeHandle delivery = FindNodeByToc(searchWin);
IMAPSCHandle dupSearchCriteria = nil;
BoxCountHandle dupBoxes = nil;
Handle dupSearch = nil;
// picky picky picky.
if (!delivery || !pers || !searchWin || !toSearch || !searchCriteria) return (false);
// we must be online
if (!bAlreadyOnline && Offline && GoOnline()) return(false);
// pass the search thread it's own copy of the search parameters -jdboyd 08/05/04
dupSearchCriteria = DupHandle((Handle)searchCriteria);
dupBoxes = DupHandle((Handle)boxesToSearch);
dupSearch = DupHandle((Handle)toSearch);
if (dupSearchCriteria && dupBoxes && dupSearch)
{
if (PrefIsSet(PREF_THREADING_OFF) || !ThreadsAvailable())
{
result = DoIMAPServerSearch(searchWin, dupBoxes, dupSearch, searchCriteria, matchAll, firstUID);
}
else
{
// collect password now if we need it.
CurPers = pers;
err = CheckIMAPSettingsForPers();
// if we were given a password, set up the thread
if (err==noErr)
{
Zero(flags);
Zero(imapInfo);
imapInfo.destToc = searchWin;
imapInfo.toSearch = dupSearch;
imapInfo.boxesToSearch = dupBoxes;
imapInfo.searchC = dupSearchCriteria;
imapInfo.matchAll = matchAll;
imapInfo.firstUID = firstUID;
imapInfo.command = IMAPSearchTask;
result = ((err=SetupXferMailThread (false, false, true, false, flags, &imapInfo))==noErr);
// and remove any old task errors
RemoveTaskErrors(IMAPSearchTask,(*CurPers)->persId);
// forget the keychain password so it's not written to the settings file
if (KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN)) Zero((*CurPers)->password);
}
CurPers = oldPers;
}
}
else
{
// error. Cleanup.
ZapHandle(dupSearchCriteria);
ZapHandle(dupBoxes);
ZapHandle(dupSearch);
WarnUser(MEM_ERR, err=MemError());
}
return (result);
}
/**********************************************************************
* DoIMAPServerSearch - make a connection and do a search on one pers
**********************************************************************/
Boolean DoIMAPServerSearch(TOCHandle searchWin, BoxCountHandle allBoxes, Handle boxesToSearch, IMAPSCHandle searchCriteria, Boolean matchAll, long firstUID)
{
Boolean result = false;
OSErr err = noErr;
IMAPStreamPtr imapStream;
PersHandle oldPers = CurPers, boxPers = nil;
MailboxNodeHandle mboxToSearch = nil;
Str255 progressMessage, mName;
short criteriaCount;
short numCriteria = GetHandleSize_(searchCriteria)/sizeof(IMAPSCStruct);
short boxCount;
short numBoxes = GetHandleSize_(boxesToSearch)/sizeof(short);
Str255 cSearchString, scratch;
Str255 pHeaders, cHeaders;
UIDNodeHandle boxSearchResults = nil;
UIDNodeHandle allScan = nil, boxScan = nil, nextScan = nil;
UIDNodeHandle allSearchResults = nil;
Boolean searchTheBody;
DeliveryNodeHandle delivery = FindNodeByToc(searchWin);
FSSpec specToSearch;
short menuId;
SignedByte pState;
unsigned long start, next, last;
unsigned long chunkLen = GetRLong(IMAP_SEARCH_CHUNK_SIZE);
// must have some place to put the results
if (!delivery || (*delivery)->aborted) return (false);
// must have a list of all mailboxes
if (!allBoxes || !*allBoxes) return (false);
// must have a list of mailboxes to search
if (!boxesToSearch || !*boxesToSearch) return (false);
// must have something to search for
if (!searchCriteria || !*searchCriteria) return (false);
// increment the delivery node since we're starting a search thread
(*delivery)->threadCount++;
// the personalitiy we're searching is the owner of the first mailbox to search ...
specToSearch.parID = (*allBoxes)[((short *)(*boxesToSearch))[0]].dirId;
specToSearch.vRefNum = (*allBoxes)[((short *)(*boxesToSearch))[0]].vRef;
menuId = (specToSearch.parID==MailRoot.dirId && SameVRef(specToSearch.vRefNum,MailRoot.vRef)) ? MAILBOX_MENU : FindDirLevel(specToSearch.vRefNum,specToSearch.parID);
MailboxMenuFile(menuId,(*allBoxes)[((short *)(*boxesToSearch))[0]].item,specToSearch.name);
specToSearch.parID = SpecDirId(&specToSearch);
LocateNodeBySpecInAllPersTrees(&specToSearch, &mboxToSearch, &CurPers);
if (CurPers)
{
// display some progress indicating which server we're searching
pState = HGetState((Handle)CurPers);
LDRef(CurPers);
ComposeRString(progressMessage,IMAP_SEARCHING_PERS,(*CurPers)->name);
HSetState(CurPers, pState);
PROGRESS_START;
PROGRESS_MESSAGE(kpTitle,progressMessage);
// Create a new IMAP stream ...
imapStream = GetIMAPConnection(IMAPSearchTask, CAN_PROGRESS);
if (imapStream)
{
// open a connection to the server ...
if (OpenControlStream(imapStream))
{
// iterate through the mailboxes to search
for (boxCount = 0; (boxCount < numBoxes) && !((*delivery)->aborted); boxCount++)
{
// figure out who this mailbox belongs to
specToSearch.parID = (*allBoxes)[((short *)(*boxesToSearch))[boxCount]].dirId;
specToSearch.vRefNum = (*allBoxes)[((short *)(*boxesToSearch))[boxCount]].vRef;
menuId = (specToSearch.parID==MailRoot.dirId && SameVRef(specToSearch.vRefNum,MailRoot.vRef)) ? MAILBOX_MENU : FindDirLevel(specToSearch.vRefNum,specToSearch.parID);
MailboxMenuFile(menuId,(*allBoxes)[((short *)(*boxesToSearch))[boxCount]].item,specToSearch.name);
specToSearch.parID = SpecDirId(&specToSearch);
LocateNodeBySpecInAllPersTrees(&specToSearch, &mboxToSearch, &boxPers);
if (mboxToSearch)
{
// open the mailbox on the server
LockMailboxNodeHandle(mboxToSearch);
if (IMAPOpenMailbox(imapStream, (*mboxToSearch)->mailboxName,false))
{
// take this opportunity to update the UIDVALIDITY of the mailbox if it hasn't been resynched before.
if ((*mboxToSearch)->uidValidity == 0)
(*mboxToSearch)->uidValidity = UIDValidity(imapStream);
UnlockMailboxNodeHandle(mboxToSearch);
// determine the range of messages ...
IMAPMailboxUIDRange(imapStream, imapStream->mailStream->nmsgs, &start, &last);
// remember the highest for later use ...
(*mboxToSearch)->searchUID = last;
// start the search in the appropriate place
if (firstUID)
start = firstUID;
// searches will start at the lowest UID in the mailbox, and go to the end
if (chunkLen)
{
// make sure we got back a valid range.
// If we didn't, we can still attempt the search with no chunking.
if (last < start)
{
start = 0;
}
}
else
{
start = last = 0; // user has requested no chunking
}
next = MIN(start + chunkLen, last);
while (!((*delivery)->aborted) && (start <= last))
{
// iterate through the criteria, and do one search for each criteria element
for (criteriaCount = 0; (criteriaCount < numCriteria) && !((*delivery)->aborted); criteriaCount++)
{
// make a c-string out of what we're searching for
PtoCcpy(cSearchString, (*searchCriteria)[criteriaCount].string);
// build the header list to search trhough
pHeaders[0] = cHeaders[0] = nil;
switch ((*searchCriteria)[criteriaCount].headerCombination)
{
case kNone:
{
PCopy(pHeaders, GetRString(scratch, (*searchCriteria)[criteriaCount].headerName));
pHeaders[0]--; //drop :
PtoCcpy(cHeaders, pHeaders);
break;
}
case kAllHeaders:
case kAnyWhere:
case kAnyRecipient:
{
BuildHeaderSearchString(((*searchCriteria)[criteriaCount].headerCombination==kAllHeaders) || ((*searchCriteria)[criteriaCount].headerCombination==kAnyWhere),
((*searchCriteria)[criteriaCount].headerCombination==kAnyRecipient), pHeaders);
PtoCcpy(cHeaders, pHeaders);
break;
}
default:
break;
}
// display some progress indicating which mailbox we're searching
LockMailboxNodeHandle(mboxToSearch);
PathToMailboxName ((*mboxToSearch)->mailboxName, mName, (*mboxToSearch)->delimiter);
UnlockMailboxNodeHandle(mboxToSearch);
ComposeRString(progressMessage,IMAP_SEARCHING_MAILBOX,mName);
PROGRESS_MESSAGE(kpSubTitle,progressMessage);
// and do the search.
searchTheBody = ((*searchCriteria)[criteriaCount].headerCombination == kBody) || ((*searchCriteria)[criteriaCount].headerCombination == kAnyWhere);
if (UIDFind(imapStream, cHeaders, searchTheBody, false, cSearchString, start, next, &boxSearchResults))
{
// no results found?
if (boxSearchResults == nil)
{
// if we're doing a match all, and this criteria failed,
// We're done with this cunk.
if (matchAll)
{
if (allSearchResults) UID_LL_Zap(&allSearchResults);
goto hell;
}
}
else
{
// prepare the search results we just got ...
for (boxScan = boxSearchResults; boxScan; boxScan = (*boxScan)->next) (*boxScan)->boxIndex = ((short *)(*boxesToSearch))[boxCount];
// now save the search results
if (allSearchResults == nil)
{
// no results yet. The results up to this point are the results of the last search
allSearchResults = boxSearchResults;
boxSearchResults = nil;
}
else
{
if (matchAll)
{
// match all. The results are what is common in what we just found and in what we have accumulated
allScan = allSearchResults;
while (allScan)
{
nextScan = (*allScan)->next;
for (boxScan = boxSearchResults; boxScan; boxScan = (*boxScan)->next)
if ((*boxScan)->uid == (*allScan)->uid) break;
if (!boxScan)
{
boxScan = (*allScan)->next;
LL_Remove(allSearchResults,allScan,(UIDNodeHandle));
ZapHandle(allScan);
}
allScan = nextScan;
}
// done with the last search results
UID_LL_Zap(&boxSearchResults);
boxSearchResults = nil;
// if allSearchResults is now empty, stop the search.
if (allSearchResults == nil) goto hell;
}
else
{
// match any. Results are what we have PLUS what we have just found.
while (boxScan = boxSearchResults)
{
boxSearchResults = (*boxScan)->next;
(*boxScan)->next = nil;
UID_LL_OrderedInsert(&allSearchResults, &boxScan, false);
}
}
}
}
} // end single mailbox, single criteria search
else // the search failed
if (CommandPeriod) AbortDeliveryNode(delivery);
} // end criteria loop
// return the search results for this mailbox now. This clears allSearchResults.
if (allSearchResults) ReturnSearchHits(imapStream, delivery, &specToSearch, &allSearchResults);
hell:
start = next+1;
next = MIN(start + chunkLen, last);
} // end chunk loop
} // end open mailbox
else
UnlockMailboxNodeHandle(mboxToSearch);
}
} // end box loop
PROGRESS_MESSAGER(kpSubTitle,CLEANUP_CONNECTION);
} // end connection
}
else
{
WarnUser(MEM_ERR, MemError());
}
CleanupConnection(&imapStream);
PROGRESS_END;
}
// aborted? decrement the thread count.
// Other threads will abort, and node will be removed at idle time from main thread
if (delivery && ((*delivery)->aborted || CommandPeriod))
(*delivery)->threadCount--;
else
{
(*delivery)->threadCount--;
if ((*delivery)->threadCount <= 0) (*delivery)->finished = true;
}
// cleanup
UID_LL_Zap(&allSearchResults);
allSearchResults = nil;
// dispose of the search criteria
ZapHandle(allBoxes);
ZapHandle (boxesToSearch);
ZapHandle(searchCriteria);
// dispose of the list of boxes we were asked to search
ZapHandle(boxesToSearch);
boxesToSearch = nil;
// dispose of our own copy of all the boxes to search
// uhhhh ... no. SearchWin depends on this list later! -JDB
//ZapHandle(allBoxes);
//allBoxes = nil;
CurPers = oldPers;
return (result);
}
/**********************************************************************
* IMAPMailboxUIDRange - look at the selected mailbox, and
* return the UID of the first and the last message
**********************************************************************/
void IMAPMailboxUIDRange(IMAPStreamPtr imapStream, unsigned long numMessages, unsigned long *first, unsigned long *last)
{
*first = *last = 0; //default
*first = FetchUID(imapStream, 1);
*last = FetchUID(imapStream, numMessages);
}
/**********************************************************************
* IMAPMailboxChanged - a message has been added to an IMAP
* mailbox.
**********************************************************************/
void IMAPMailboxChanged(MailboxNodeHandle mbox)
{
if (!PrefIsSet(PREF_NO_LIVE_SEARCHES))
{
SetIMAPMailboxNeeds(mbox, kNeedsSearch, true);
gUpdateIncrementalSearches = true;
}
}
/**********************************************************************
* UpdateIncrementalIMAPSearches - go through all IMAP mailboxes that
* have changed recently and make sure all live searches are updated
**********************************************************************/
void UpdateIncrementalIMAPSearches(void)
{
if (gUpdateIncrementalSearches)
{
if (!PrefIsSet(PREF_NO_LIVE_SEARCHES) && !GetNumBackgroundThreads() && !IMAPFilteringUnderway())
{
IMAPUpdateIncrementalSearches();
gUpdateIncrementalSearches = false;
}
}
}
/**********************************************************************
* ReturnSearchHits - given a UIDNode list, return the results
* to the main thread.
*
* - pull out all UIDs that exist locally, and return them.
* - fetch minimal headers of those that don't
* - wait for them to download, and then return them.
**********************************************************************/
Boolean ReturnSearchHits(IMAPStreamPtr imapStream, DeliveryNodeHandle searchNode, FSSpecPtr spec, UIDNodeHandle *uids)
{
Boolean result = false;
DeliveryNodeHandle deliveryNode;
TOCHandle tocToSync = TOCBySpec(spec);
MailboxNodeHandle mailbox = nil;
UIDNodeHandle uidsToFetch = nil, node, next;
long sumNum;
TOCHandle hidTocH;
// must have a TOC to sync
if (!tocToSync) return (false);
// must have a delivery node for the search results
if (!searchNode) return (false);
// must have some UIDs to fetch
if (!uids || !*uids) return (true);
// if there's already a delivery node set up for this TOC, then the resync is already underway. Just return all the results
if (tocToSync && FindNodeByToc(tocToSync))
{
BuildSearchResults(searchNode, *uids);
UID_LL_Zap(uids);
*uids = nil;
return (true);
}
// must be connected and have a mailbox selected
if (!imapStream || !IsConnected(imapStream->mailStream) || !IsSelected(imapStream->mailStream)) return (false);
// UIDFetchMessages needs to have the mailbox information
mailbox = TOCToMbox(tocToSync);
if (!mailbox)
return (false);
// may have to consider locally hidden toc entries
hidTocH = GetHiddenCacheMailbox(mailbox, false, false);
// build a list of uids to fetch
next = nil;
for (node = *uids; node; node = next)
{
next = (*node)->next;
// if the summary can't be found ...
if ((TOCFindMessByMID((*node)->uid,tocToSync,&sumNum) != noErr)
&& (!hidTocH || (TOCFindMessByMID((*node)->uid,hidTocH,&sumNum) != noErr)))
{
// remove this node from the search results ...
LL_Remove(*uids, node, (UIDNodeHandle));
(*node)->next = nil;
// and put it in the list of uids to fetch.
LL_Queue(uidsToFetch, node, (UIDNodeHandle));
}
}
// Return the remaining results. These messages exist locally.
if (*uids && **uids)
{
BuildSearchResults(searchNode, *uids);
UID_LL_Zap(uids);
*uids = nil;
}
// go fetch the missing summaries
if (uidsToFetch && *uidsToFetch)
{
// set up the structure that the summaries will be returned in.
deliveryNode = NewDeliveryNode(tocToSync);
if (deliveryNode)
{
(*deliveryNode)->filter = false;
// queue it in the global list of deliveries. It will be removed in the idle loop or the thread
QueueDeliveryNode(deliveryNode);
result = UIDFetchMessages(imapStream, mailbox, uidsToFetch, deliveryNode, false);
if (result)
{
(*deliveryNode)->finished = true;
// no threading. Get results immediately
if ((PrefIsSet(PREF_THREADING_OFF) || !ThreadsAvailable())) UpdateIMAPMailbox(tocToSync);
// else
// hope Alan remember to UpdateIMAPMailbox() before grabbing summaries.
// and return the new results
BuildSearchResults(searchNode, uidsToFetch);
}
else
{
// about this deliverynode, let the idle routine dequeue it.
AbortDeliveryNode(deliveryNode);
}
// Cleanup.
UID_LL_Zap(&uidsToFetch);
uidsToFetch = nil;
}
else
WarnUser(MEM_ERR,MemError());
}
return (result);
}
/**********************************************************************
* BuildSearchResults - convert from a UIDNode list to something the
* search window can understand
**********************************************************************/
void BuildSearchResults(DeliveryNodeHandle delivery, UIDNodeHandle results)
{
long numResults = 0;
long offset = 0;
UIDNodeHandle resultScan;
IMAPSResultStruct imapResult;
OSErr err = noErr;
SignedByte state;
// must have some results
if (results && *results)
{
// must have someplace to put the results
if (delivery && !(*delivery)->aborted)
{
// allocate a handle big enough for all the results
for (resultScan = results; resultScan; resultScan = (*resultScan)->next) numResults++;
state = HGetState((Handle)delivery);
LDRef(delivery);
if ((*delivery)->results)
{
offset = GetHandleSize((*delivery)->results);
SetHandleBig_((*delivery)->results,offset + (numResults*sizeof(IMAPSResultStruct)));
if (err=MemError())
{
WarnUser(MEM_ERR, err);
HSetState(delivery, state);
return;
}
}
else
{
(*delivery)->results = NuHTempBetter(numResults*sizeof(IMAPSResultStruct));
if (!(*delivery)->results)
{
WarnUser(MEM_ERR, MemError());
HSetState(delivery, state);
return;
}
}
// put the results into the handle
for ((resultScan=results) && (offset=0); resultScan; (resultScan=(*resultScan)->next) && offset++)
{
imapResult.box = (*resultScan)->boxIndex;
imapResult.uidHash = (*resultScan)->uid;
BMD(&imapResult,(*((*delivery)->results))+offset,sizeof(IMAPSResultStruct));
}
HSetState(delivery, state);
}
}
}
/**********************************************************************
* IMAPAbortSearch - stop the search in progress
**********************************************************************/
void IMAPAbortSearch(TOCHandle searchWin)
{
DeliveryNodeHandle node = FindNodeByToc(searchWin);
OSErr err = noErr;
threadDataHandle index;
SignedByte state;
// Is there a search going on this window?
if (node)
{
// has the search finished?
if (!(*node)->finished)
{
// if not, mark the node as aborted.
AbortDeliveryNode(node);
// set command period for all the search threads for this window
for (index=gThreadData;index;index=(*index)->next)
{
// is this a search thread?
if ((*index)->currentTask == IMAPSearchTask)
{
// does this search thread belong to the current search window?
state = HGetState((Handle)index);
LDRef(index);
if (SameTOC((*index)->imapInfo.destToc, searchWin))
{
// cancel the thread
SetThreadGlobalCommandPeriod ((*index)->threadID, true);
// continue through all threads like this
}
HSetState((Handle)index,state);
}
}
}
else
{
// the search is finished, we can kill the node.
DequeueDeliveryNode(node);
ZapDeliveryNode(&node);
}
}
}
/**********************************************************************
* IMAPStartFiltering - prepare to talk to the server about messages.
* NOT thread safe
**********************************************************************/
Boolean IMAPStartFiltering(TOCHandle tocToFilter, Boolean connect)
{
Boolean result = false;
MailboxNodeHandle boxToFilter = nil;
PersHandle oldPers = CurPers;
Str255 progressMessage, mName;
// must have something to filter
if (!tocToFilter) return (false);
// if we're already connected, nothing to do.
if (gFilterStream != nil && IsSelected(gFilterStream->mailStream)) return (true);
// don't display any progress during filtering
gFilteringUnderway = true;
// if asked, open a connection to the mailbox tocToFilter
if (connect)
{
boxToFilter = TOCToMbox(tocToFilter);
gFilterPers = TOCToPers(tocToFilter);
if (boxToFilter && gFilterPers)
{
oldPers = CurPers;
CurPers = gFilterPers;
// display some progress information
LockMailboxNodeHandle(boxToFilter);
PathToMailboxName ((*boxToFilter)->mailboxName, mName, (*boxToFilter)->delimiter);
UnlockMailboxNodeHandle(boxToFilter);
ComposeRString(progressMessage,IMAP_SEARCHING_MAILBOX,mName);
PROGRESS_MESSAGE(kpSubTitle,progressMessage);
// Connect to the server
if (gFilterStream || (gFilterStream = GetIMAPConnection(IMAPSearchTask, CAN_PROGRESS)))
{
// Select the mailbox
LockMailboxNodeHandle(boxToFilter);
if ((result = IMAPOpenMailbox(gFilterStream, (*boxToFilter)->mailboxName,false))==false)
{
IMAPError(kIMAPSearching, kIMAPSelectMailboxErr, errIMAPSearchMailboxErr);
CleanupConnection(&gFilterStream);
gFilterStream = nil;
gFilteringUnderway = false;
CommandPeriod = true; // stop all filtering
}
UnlockMailboxNodeHandle(boxToFilter);
}
else
{
if (!CommandPeriod) WarnUser(MEM_ERR, MemError());
gFilteringUnderway = false;
CommandPeriod = true; // stop all filtering
}
CurPers = oldPers;
}
}
return (result || !connect);
}
/**********************************************************************
* IMAPTermMatch - does an IMAP message match a term?
* NOT thread safe
**********************************************************************/
Boolean IMAPTermMatch(MTPtr mt, MSumPtr sum)
{
Boolean match = false;
UIDNodeHandle searchResults = nil;
Str255 cSearchString;
Str255 pHeaders, cHeaders;
// must have a connection open to the server ...
if (!gFilterStream || !IsSelected(gFilterStream->mailStream))
{
IMAPError(kIMAPSearching, kIMAPNotConnectedErr, errIMAPSearchMailboxErr);
CommandPeriod = true;
return (false);
}
// build the string we're looking for ...
PtoCcpy(cSearchString, mt->value);
// build the header to look for ...
pHeaders[0] = cHeaders[0] = nil;
switch (mt->headerID)
{
case FILTER_ANY:
case FILTER_ADDRESSEE:
{
BuildHeaderSearchString(mt->headerID==FILTER_ANY, mt->headerID==FILTER_ADDRESSEE, pHeaders);
PtoCcpy(cHeaders, pHeaders);
break;
}
case FILTER_BODY:
break;
default:
{
// the header name ends with a ':'. Remove it.
PCopy(pHeaders, mt->header);
pHeaders[0]--;
PtoCcpy(cHeaders, pHeaders);
break;
}
}
// and do the search ...
if (UIDFind(gFilterStream, cHeaders, mt->headerID==FILTER_BODY, mt->verb==mbmNotContains, cSearchString, sum->uidHash, sum->uidHash, &searchResults))
{
if (searchResults && *searchResults)
{
if ((*searchResults)->uid == sum->uidHash) match = true;
ZapHandle(searchResults);
}
}
else // the search failed. Stop filtering.
CommandPeriod = true;
return (match);
}
/**********************************************************************
* BuildHeaderSearchString - build the list of headers to search for
* when searching on Any Header or AnyRecipient
**********************************************************************/
void BuildHeaderSearchString(Boolean anyHeader, Boolean anyRec, Str255 pHeaders)
{
pHeaders[0] = nil;
if (anyHeader)
{
PCatR(pHeaders,HEADER_STRN+FROM_HEAD);
pHeaders[pHeaders[0]] = ','; // turn : into a ,
PCatR(pHeaders,HEADER_STRN+SUBJ_HEAD);
pHeaders[pHeaders[0]] = ','; // turn : into a ,
PCatR(pHeaders,HEADER_STRN+ATTACH_HEAD);
pHeaders[pHeaders[0]] = ','; // turn : into a ,
PCatR(pHeaders,HEADER_STRN+REPLYTO_HEAD);
pHeaders[pHeaders[0]] = ','; // turn : into a ,
PCatR(pHeaders,HEADER_STRN+IN_REPLY_TO_HEAD);
pHeaders[pHeaders[0]] = ','; // turn : into a ,
PCatR(pHeaders,HEADER_STRN+DATE_HEAD);
pHeaders[pHeaders[0]] = ','; // turn : into a ,
}
if (anyRec || anyHeader)
{
PCatR(pHeaders,HEADER_STRN+TO_HEAD);
pHeaders[pHeaders[0]] = ','; // turn : into a ,
PCatR(pHeaders,HEADER_STRN+CC_HEAD);
pHeaders[pHeaders[0]] = ','; // turn : into a ,
}
// drop last comma
pHeaders[0]--;
return;
}
/**********************************************************************
* IMAPStopFiltering - clean up after filtering.
* NOT thread safe
**********************************************************************/
void IMAPStopFiltering(Boolean reallyDone)
{
if (gFilterStream != nil)
{
// if we're really done filering, display some progress
if (reallyDone) PROGRESS_MESSAGER(kpSubTitle,CLEANUP_CONNECTION);
CleanupConnection(&gFilterStream);
gFilterStream = nil;
gFilterPers = nil;
}
if (reallyDone)
{
gFilteringUnderway = false;
gManualFilteringUnderway = 0;
}
return;
}
/**********************************************************************
* IMAPFilteringUnderway - return true if we're in the middle of
* filtering messages.
**********************************************************************/
Boolean IMAPFilteringUnderway(void)
{
return (gFilteringUnderway);
}
/**********************************************************************
* IMAPStartManualFiltering - manual filtering is now underway
**********************************************************************/
void IMAPStartManualFiltering(void)
{
gManualFilteringUnderway++;
}
/**********************************************************************
* IMAPFilteringWarnOffline - return true if the user should be alerted
* about going online during manual filtering
**********************************************************************/
Boolean IMAPFilteringWarnOffline(void)
{
Boolean warn = true;
// if the user is manually filtering, and hasn't yet been warned, warn him.
if (gManualFilteringUnderway==1)
{
gManualFilteringUnderway++;
}
else
{
// otherwise, don't if we're filtering
warn = !IMAPFilteringUnderway();
}
return (warn);
}
/**********************************************************************
* IMAPProccessBoxesMainThread - fire off threads to handle IMAP
* mailboxes now that they're idle.
**********************************************************************/
void IMAPProccessBoxesMainThread(Boolean bResync, Boolean bExpunge, Boolean bSearch)
{
PersHandle pers;
SignedByte state;
// go through all mailboxes, resynching and expunging the ones that need it.
for (pers = PersList; pers; pers = (*pers)->next)
{
if (IsIMAPPers(pers))
{
if ((*pers)->mailboxTree)
{
state = HGetState((Handle)pers);
LDRef(pers);
IMAPMailboxPostProcess((*pers)->mailboxTree, bResync, bExpunge, bSearch);
HSetState(pers, state);
}
}
}
}
/**********************************************************************
* IMAPMailboxPostProcess - resync the mailboxes in the tree that
* are marked. NOT thread safe!
**********************************************************************/
void IMAPMailboxPostProcess(MailboxNodeHandle tree, Boolean resync, Boolean expunge, Boolean search)
{
MailboxNodeHandle scan = nil;
TOCHandle scanTOC = nil;
// first, go through and expunge all mailboxes that need it
if (expunge)
{
scan = tree;
while (scan)
{
if (DoesIMAPMailboxNeed(scan, kNeedsExpunge))
{
SetIMAPMailboxNeeds(scan, kNeedsExpunge, false);
LockMailboxNodeHandle(scan);
if (scanTOC=FindTOC(&((*scan)->mailboxSpec)))
{
// expunge the mailbox
DoExpungeMailboxLo(scanTOC, false);
}
UnlockMailboxNodeHandle(scan);
}
// check out the children
if ((*scan)->childList)
{
LockMailboxNodeHandle(scan);
IMAPMailboxPostProcess((*scan)->childList, resync, expunge, search);
UnlockMailboxNodeHandle(scan);
}
scan = (*scan)->next;
}
}
if (resync)
{
// now go through and start resyncs on all mailboxes that need it.
scan = tree;
while (scan)
{
// is this mailbox marked for resync?
if (DoesIMAPMailboxNeed(scan,kNeedsResync))
{
if (PrefIsSet(PREF_FOREGROUND_IMAP_FILTERING))
SetIMAPMailboxNeeds(scan, kNeedsResync, false);
// is it visible?
LockMailboxNodeHandle(scan);
if (scanTOC=FindTOC(&((*scan)->mailboxSpec)))
{
if ((*scanTOC)->win && IsWindowVisible(GetMyWindowWindowPtr((*scanTOC)->win)))
{
SetIMAPMailboxNeeds(scan, kNeedsResync, false);
FetchNewMessages(scanTOC, true, false, false, false);
}
}
UnlockMailboxNodeHandle(scan);
}
// check out the children
if ((*scan)->childList)
{
LockMailboxNodeHandle(scan);
IMAPMailboxPostProcess((*scan)->childList, resync, expunge, search);
UnlockMailboxNodeHandle(scan);
}
scan = (*scan)->next;
}
}
if (search)
{
// now go through and update incremental searches
scan = tree;
while (scan)
{
// is this mailbox marked as having changed?
if (DoesIMAPMailboxNeed(scan,kNeedsSearch))
{
SetIMAPMailboxNeeds(scan, kNeedsSearch, false);
// update all incremental searches
IMAPSearchIncremental(scan);
}
// check out the children
if ((*scan)->childList)
{
LockMailboxNodeHandle(scan);
IMAPMailboxPostProcess((*scan)->childList, resync, expunge, search);
UnlockMailboxNodeHandle(scan);
}
scan = (*scan)->next;
}
}
}
/**********************************************************************
* PrefMakeAttachmentStub - given the size of a part, and the myriad
* of download preferences, return true if we should make a stub.
**********************************************************************/
Boolean PrefMakeAttachmentStub(long size)
{
Boolean make = true;
long bigMessage = GetRLong(BIG_MESSAGE) K;
// Fetch everything
if (PrefIsSet(PREF_IMAP_FULL_MESSAGE_AND_ATTACHMENTS)) make = false;
// Fetch attachments up to x K
else if (PrefIsSet(PREF_IMAP_FULL_MESSAGE) && (size < bigMessage)) make = false;
return (make);
}
/**********************************************************************
* FlagForResync - given a mailbox, set a flag so it gets resynced
**********************************************************************/
void FlagForResync(TOCHandle tocH)
{
MailboxNodeHandle box = TOCToMbox(tocH);
if (box)
SetIMAPMailboxNeeds(box, kNeedsResync, true);
}
/**********************************************************************
* FlagForExpunge - given a mailbox, set a flag so it gets expunged
**********************************************************************/
void FlagForExpunge(TOCHandle tocH)
{
MailboxNodeHandle box = TOCToMbox(tocH);
if (box)
SetIMAPMailboxNeeds(box, kNeedsExpunge, true);
}
/**********************************************************************
* IMAPTocHBusy - mark an IMAP mailbox as busy or not. This prevents
* it's toc from getting flushed.
**********************************************************************/
void IMAPTocHBusy(TOCHandle tocH, Boolean busy)
{
MailboxNodeHandle box = TOCToMbox(tocH);
if (box)
{
if (busy) (*box)->tocRef++;
else if ((*box)->tocRef) (*box)->tocRef--;
}
}
/**********************************************************************
* ResyncOpenMailboxes - resync all open IMAP mailboxes
**********************************************************************/
void ResyncOpenMailboxes(PersHandle pers)
{
OSErr err = noErr;
Accumulator mailboxesAcc;
MailboxNodeHandle b = nil;
PersHandle p;
TOCHandle tocH;
FSSpec boxSpec;
MailboxNodeHandle inbox = LocateInboxForPers(pers);
// build the list of mailboxes to resynchronize
AccuInit(&mailboxesAcc);
for (tocH=TOCList; tocH && (err == noErr); tocH = (*tocH)->next)
{
boxSpec = GetMailboxSpec(tocH,-1);
b = TOCToMbox(tocH);
p = TOCToPers(tocH);
if (p && (p == pers) && b && (b!=inbox))
{
err = AccuAddPtr(&mailboxesAcc, &boxSpec, sizeof(FSSpec));
}
}
AccuTrim(&mailboxesAcc);
if ((err == noErr) && (mailboxesAcc.size > 0))
{
// resynchronize the mailboxes
err = IMAPProcessMailboxes(mailboxesAcc.data,IMAPMultResyncTask);
}
else
{
// an error occurred. Clean up the list
AccuZap(mailboxesAcc);
}
}
/**********************************************************************
* ResetFilterFlags - given a mailbox, reset the summaries need to
* filter flag.
**********************************************************************/
void ResetFilterFlags(TOCHandle tocH)
{
short count;
// only do this to IMAP mailboxes
if ((*tocH)->imapTOC)
{
for (count = 0; count < (*tocH)->count; count++)
(*tocH)->sums[count].flags &= ~FLAG_UNFILTERED;
}
}
/**********************************************************************
* NeedCleanUpAttachmentsAfterIMAPTransfer - return true if this IMAP
* message's attachments ought to be deleted.
**********************************************************************/
Boolean NeedCleanUpAttachmentsAfterIMAPTransfer(TOCHandle tocH, short sumNum)
{
Boolean deleteThem = false;
MailboxNodeHandle mbox = nil;
PersHandle oldPers = CurPers, pers = nil;
// only makes sense to check IMAP toc's
if (tocH && (*tocH)->imapTOC)
{
// is the pref set to clean up after a transfer?
mbox = TOCToMbox(tocH);
pers = TOCToPers(tocH);
if (mbox && pers)
{
CurPers = pers;
if (!PrefIsSet(PREF_IMAP_SAVE_ORPHANED_ATT))
{
deleteThem = true;
}
CurPers = oldPers;
}
}
return (deleteThem);
}
/**********************************************************************
* CleanUpAttachmentsAfterSingleIMAPTransfer - clean up an IMAP message's
* attachments after a transfer.
**********************************************************************/
void CleanUpAttachmentsAfterIMAPTransfer(TOCHandle tocH, short sumNum)
{
if (NeedCleanUpAttachmentsAfterIMAPTransfer(tocH, sumNum))
{
// move this message's attachments to the trash.
MovingAttachments(tocH,sumNum,True,false,True,false);
}
}
/**********************************************************************
* PrepareDownloadProgress() - set up the stream to properly display
* progress,
**********************************************************************/
void PrepareDownloadProgress(IMAPStreamPtr imapStream, long totalSize)
{
imapStream->mailStream->totalTransfer = totalSize;
imapStream->mailStream->currentTransfer = 0;
imapStream->mailStream->lastProgress = TickCount();
}
/************************************************************************
* IMAPFetchMessageHeadersForFiltering - return a handle to the remote
* message's headers.
************************************************************************/
Handle IMAPFetchMessageHeadersForFiltering(TOCHandle tocH, short sumNum)
{
Boolean result = false;
PersHandle oldPers = CurPers;
PersHandle pers = nil;
MailboxNodeHandle mailboxNode = nil;
unsigned long uid;
Handle headers = nil;
// must have a toc
if (!tocH) return (nil);
// must be an IMAP toc
if (!(*tocH)->imapTOC) return (nil);
// sumNum must have a uid
uid = (*tocH)->sums[sumNum].uidHash;
if (!(uid > 0)) return (nil);
// must have a connection open to the server ...
if (!gFilterStream || !IsSelected(gFilterStream->mailStream))
{
IMAPError(kIMAPSearching, kIMAPNotConnectedErr, errIMAPSearchMailboxErr);
CommandPeriod = true;
return (nil);
}
// see if this is indeed an IMAP mailbox
mailboxNode = TOCToMbox(tocH);
pers = TOCToPers(tocH);
if (mailboxNode && pers)
{
CurPers = pers;
if (UIDFetchHeader(gFilterStream, uid, false))
{
if (gFilterStream->mailStream->fNetData && *(gFilterStream->mailStream->fNetData))
{
headers = gFilterStream->mailStream->fNetData;
gFilterStream->mailStream->fNetData = nil;
}
}
CurPers = oldPers;
}
else
IMAPError(kIMAPFetch, kIMAPNotIMAPMailboxErr, errNotIMAPMailboxErr);
// convert /r/n to /r
RNtoR(headers);
return (headers);
}
/************************************************************************
* GetNextWaitingMailboxNode - return a pointer the the next IMAP node
* waiting to be filtered
************************************************************************/
MailboxNodeHandle GetNextWaitingMailboxNode(MailboxNodeHandle tree)
{
MailboxNodeHandle scan = tree;
MailboxNodeHandle node = nil;
while (scan)
{
// is this the node we're looking for?
if (DoesIMAPMailboxNeed(scan, kNeedsFilter))
{
node = scan;
break;
}
// is the node we're looking for one of the children of this node?
LockMailboxNodeHandle(scan);
if ((*scan)->childList) node = GetNextWaitingMailboxNode(((*scan)->childList));
UnlockMailboxNodeHandle(scan);
if (node) break;
// otherwise, check the next node.
scan = (*scan)->next;
}
// Return the node
return (node);
}
/************************************************************************
* GetNextWaitingIMAPToc - return a pointer the the next IMAP toc waiting
* to be filtered
************************************************************************/
void GetNextWaitingIMAPToc(TOCHandle *toc)
{
FSSpec spec;
PersHandle pers;
MailboxNodeHandle scan = nil;
TOCHandle tocH = nil, hidTocH = nil;
SignedByte state;
for (pers=PersList;pers;pers=(*pers)->next)
{
state = HGetState((Handle)pers);
LDRef(pers);
scan = GetNextWaitingMailboxNode((*pers)->mailboxTree);
HSetState((Handle)(pers), state);
if (scan) break;
}
if (scan)
{
spec = (*scan)->mailboxSpec;
tocH = TOCBySpec(&spec);
if (tocH)
{
//are there no unfiltered messages in this toc?
if (CountFlaggedMessages(tocH) == 0)
{
// then check the hidden toc
hidTocH = GetHiddenCacheMailbox(scan, false, false);
// return the hidden toc if there are unfiltered messages in it
if (hidTocH && CountFlaggedMessages(hidTocH))
tocH = hidTocH;
}
}
else
{
// something bad happened trying to open this mailbox.
// forget about filtering it.
SetIMAPMailboxNeeds(scan, kNeedsFilter, false);
}
}
if (toc)
*toc = tocH;
}
/************************************************************************
* IMAPFilterProgress - filter incoming messages for an IMAP personality.
* For now, all this does is start a dumb thread to monitor filter
* progress and display something in the TP window.
************************************************************************/
OSErr IMAPFilterProgress(TOCHandle tocH)
{
OSErr err = noErr;
XferFlags flags;
threadDataHandle index;
IMAPTransferRec imapInfo;
// notify thread of filter target
gFilterTocH = tocH;
gNumUnfiltered = tocH ? IMAPCountUnfilteredMessages(TOCToMbox(tocH)) : 0;
if (gNumUnfiltered)
{
// is there already a thread set up to filter this toc?
for (index=gThreadData; index; index=(*index)->next)
{
if ((*index)->currentTask == IMAPFilterTask)
return (noErr);
}
// nope. Set up the thread.
if (!PrefIsSet(PREF_THREADING_OFF) && ThreadsAvailable())
{
Zero(flags);
Zero(imapInfo);
PushPers(CurPers);
CurPers = TOCToPers(tocH);
imapInfo.command = IMAPFilterTask;
err = SetupXferMailThread(false, false, true, false, flags, &imapInfo);
// ... and remove any old task errors
RemoveTaskErrors(IMAPFilterTask,(*CurPers)->persId);
PopPers();
}
// watch for cancel
gFilteringCancelled = false;
}
return (err);
}
/************************************************************************
* IMAPCountUnfilteredMessages - count the number of unfiltered messages
* in an IMAP mailbox.
************************************************************************/
long IMAPCountUnfilteredMessages(MailboxNodeHandle mbox)
{
long count = 0;
TOCHandle tocH;
if (mbox)
{
tocH = FindTOC(&((*mbox)->mailboxSpec));
if (tocH)
{
count = CountFlaggedMessages(tocH);
// look in the hidden mailbox as well ...
tocH = GetHiddenCacheMailbox(mbox, false, false);
if (tocH)
count += CountFlaggedMessages(tocH);
}
}
return (count);
}
/************************************************************************
* DoIMAPFilterProgress - filter incoming messages for an IMAP
* personality.
************************************************************************/
OSErr DoIMAPFilterProgress(void)
{
OSErr err = noErr;
TOCHandle tocH = 0;
Str255 progressMessage;
MailboxNodeHandle mBox;
short initial = gNumUnfiltered;
short remaining = 0;
PushPers(CurPers);
PROGRESS_START;
// display progress while this mailbox is being filtered
while ((err == noErr) && gFilterTocH && !gFilteringCancelled)
{
// check for changes ...
if (tocH != gFilterTocH)
{
tocH = gFilterTocH;
// which mailbox is being filtered?
mBox = TOCToMbox(tocH);
CurPers = TOCToPers(tocH);
if (!CurPers) CurPers = PersList;
// update the progress message
ComposeRString(progressMessage, IMAP_FILTERING_MESSAGES, (*CurPers)->name);
PROGRESS_MESSAGE(kpTitle,progressMessage);
PROGRESS_MESSAGER(kpSubTitle,LEFT_TO_FILTER);
remaining = 0;
}
if (remaining != gNumUnfiltered)
{
remaining = gNumUnfiltered;
PROGRESS_BAR(100 - (remaining * 100)/initial,remaining,nil,nil,nil);
}
// sit and spin.
CycleBalls();
err = MyYieldToAnyThread();
// cancel filtering
if (CommandPeriod)
gFilteringCancelled = true;
}
PROGRESS_END;
PopPers();
return (err);
}
/**********************************************************************
* IMAPCancelFiltering - mark all mailboxes that need to be filtered
* as not. NOT THREAD SAFE
**********************************************************************/
void IMAPFilteringCancelled(Boolean bOverride)
{
TOCHandle tocH;
MailboxNodeHandle node;
short count;
// was filtering cancelled?
if (gFilteringCancelled || bOverride)
{
do
{
GetNextWaitingIMAPToc(&tocH);
if (tocH)
{
node = TOCToMbox(tocH);
// mailbox no longer needs filtering
SetIMAPMailboxNeeds(node, kNeedsFilter, false);
// reset filter flag and show messages, unless we just quit.
if (!AmQuitting)
{
for (count = (*tocH)->count - 1; count >= 0 ; count--)
{
if ((*tocH)->sums[count].flags & FLAG_UNFILTERED)
{
(*tocH)->sums[count].flags &= ~FLAG_UNFILTERED;
ShowHideFilteredSummary(tocH, count);
}
}
}
}
} while (tocH);
gFilteringCancelled = false;
}
}
/**********************************************************************
* RNtoR() - convert /r/n to /r. Useful 'cause IMAP code gives back
* exactly what was received on the network.
**********************************************************************/
void RNtoR(Handle text)
{
SignedByte state;
long textSize;
char *scan, *fix;
if (text)
{
state = HGetState(text);
LDRef(text);
textSize = GetHandleSize(text);
scan = *text;
while (scan < (*text + textSize))
{
if ((*scan == '\r') && (*(scan+1) == '\n'))
{
scan++;
fix = scan;
while (fix < (*text + textSize - 1))
{
*fix = *(fix + 1);
fix++;
}
textSize--;
}
else
scan++;
}
SetHandleSize(text, textSize);
HSetState(text, state);
}
}
/**********************************************************************
* SpecialCaseDownload - recurse through this body, return true if
* we should download this message in one big chunk because
* - it's got a registration part
* - we're too supid to handle this
**********************************************************************/
Boolean SpecialCaseDownload(IMAPBODY *bodyInQuestion)
{
Boolean result = false;
PART *part = nil;
IMAPBODY *body = nil;
PARAMETER *param = nil;
MIMEMap mm;
Str255 name, typeString, subTypeString, espString;
// Must have a body.
if (!bodyInQuestion) return (false);
//
// Can't yet handle messages that contain sub messages.
//
if (bodyInQuestion->type == TYPEMESSAGE) return (true);
//
// Look for X-Eudora-Plugin-Info header, see if a plugin needs this message
//
GetRString(espString, PLUGIN_INFO);
param = bodyInQuestion->parameter;
while (param)
{
if (param->attribute)
if (!pstrincmp(param->attribute,espString,espString[0]))
{
// Someday, when we wish to be smarter, pass the value parameter to the translators to see if anyone needs this message.
// For now, just fetch it. Peanut's the only plugin that uses it now.
return (true);
}
param = param->next;
}
//
// Loop through all parts, looking for special cases
//
part = bodyInQuestion->nested.part;
while (part && !result)
{
body = &(part->body);
if (body)
{
if (body->type == TYPEMESSAGE) // this might be a sub message
{
result = true;
}
else if ((body->type == TYPEAPPLICATION)) // this might be a registration part
{
if (body->subtype)
{
// look closer at the attachment part
GetFilenameParameter(body, name);
if (*name)
{
// make a reasonable guess at this part's type and creator
subTypeString[0] = strlen(body->subtype);
strncpy(subTypeString+1, body->subtype, MIN(subTypeString[0], sizeof(subTypeString)));
if (FindMIMEMapPtr(BodyTypeCodeToPString(body->type, typeString),subTypeString,name,&mm))
{
// If our MIME map tells us this is a 'eReg' file, download the whole message
if (REG_FILE_TYPE == mm.type)
result = true;
}
}
}
}
else // check the nested body
{
result = SpecialCaseDownload(body);
}
}
// Next part.
part = part->next;
}
return (result);
}
/************************************************************************
* IsIMAPOperationUnderway - return true if the specificed operation is
* underway.
************************************************************************/
Boolean IsIMAPOperationUnderway(TaskKindEnum task)
{
Boolean result = false;
threadDataHandle index;
if (task != UndefinedTask)
{
for (index=gThreadData;index && !result;index=(*index)->next)
{
if ((*index)->imapInfo.command == task)
result = true;
}
}
return (result);
}
/************************************************************************
* IsIMAPMailboxBusy - return true if this IMAP mailbox is in use and
* absolutely should not be closed.
************************************************************************/
Boolean IsIMAPMailboxBusy(TOCHandle tocH)
{
threadDataHandle index;
MailboxNodeHandle mbox;
if (tocH && (*tocH)->imapTOC)
{
// has this tocH been added to the delivery queue, or
// has someone asked to keep it around?
mbox = TOCToMbox(tocH);
if (mbox && ((*mbox)->tocRef > 0))
return true;
// is this toc in use by a thread somewhere?
for (index=gThreadData;index;index=(*index)->next)
{
if (SameTOC((*index)->imapInfo.sourceToc, tocH)
|| SameTOC((*index)->imapInfo.destToc, tocH)
|| SameTOC((*index)->imapInfo.delToc, tocH))
return true;
}
}
return false;
}
/************************************************************************
* IMAPRemoveSelectedCachedContents - wipe the local cache of the
* selected messages.
************************************************************************/
void IMAPRemoveSelectedCachedContents(TOCHandle tocH)
{
short c = CountSelectedMessages(tocH);
short sumNum;
for (sumNum=0;sumNum<(*tocH)->count && c;sumNum++)
{
if ((*tocH)->sums[sumNum].selected)
{
CycleBalls();
IMAPRemoveCachedContents(tocH, sumNum);
}
}
}
/************************************************************************
* IMAPRemoveCachedContents - wipe the local cache of a message
************************************************************************/
void IMAPRemoveCachedContents(TOCHandle tocH, short sumNum)
{
// don't do anything to minimal headers. Nothing to remove.
if ((*tocH)->sums[sumNum].offset > -1)
{
// delete any attachments associated with this message
if ((*tocH)->sums[sumNum].opts & OPT_HAS_SPOOL) RemSpoolFolder((*tocH)->sums[sumNum].uidHash);
if ((*tocH)->sums[sumNum].flags&FLAG_HAS_ATT)
{
// move the attachments to the trash if they haven't been orphaned.
if (!((*tocH)->sums[sumNum].opts & OPT_ORPHAN_ATT))
{
MovingAttachments(tocH,sumNum,True,false,True,false); // attachments
MovingAttachments(tocH,sumNum,False,false,True,false); // inline parts
}
}
// close the message window
if ((MessHandle)(*tocH)->sums[sumNum].messH) CloseMyWindow(GetMyWindowWindowPtr((*(MessHandle)(*tocH)->sums[sumNum].messH)->win));
// adjust the preview pane
if ((*tocH)->previewID==(*tocH)->sums[sumNum].serialNum && (*tocH)->previewPTE)
Preview(tocH,-1);
// clean up the summary
ZapHandle((*tocH)->sums[sumNum].cache);
DeleteMesgError(tocH,sumNum);
(*tocH)->sums[sumNum].offset = imapNeedToDownload;
(*tocH)->sums[sumNum].bodyOffset = 0;
(*tocH)->sums[sumNum].msgIdHash = 0;
(*tocH)->sums[sumNum].subjId = 0;
(*tocH)->sums[sumNum].mesgErrH = nil;
(*tocH)->sums[sumNum].cache = nil;
(*tocH)->sums[sumNum].messH = nil;
// Clear decoding errors from Removed messages
(*tocH)->sums[sumNum].flags &= ~FLAG_SKIPPED;
if ((*tocH)->sums[sumNum].state == MESG_ERR) (*tocH)->sums[sumNum].state = READ;
#ifdef NOBODY_SPECIAL
// Clear the nobody flag
(*tocH)->sums[sumNum].opts &= ~OPT_JUSTSUB;
#endif //NOBODY_SPECIAL
InvalSum(tocH, sumNum);
}
else
(*tocH)->sums[sumNum].offset = imapNeedToDownload;
}
/************************************************************************
* IMAPFetchSelectedMessages - fetch the selected messages
************************************************************************/
void IMAPFetchSelectedMessages(TOCHandle tocH, Boolean attach)
{
Handle uids = nil;
short c;
short sumNum;
//
// download attachments of messages that are already present if attach
//
if (attach)
{
for (sumNum=0;sumNum<(*tocH)->count;sumNum++)
{
if ((*tocH)->sums[sumNum].selected)
if (IMAPMessageDownloaded(tocH,sumNum))
FetchAllIMAPAttachments(tocH, sumNum, false);
}
}
//
// Fetch Messages that haven't been fetched yet
//
if ((c= CountSelectedMessages(tocH))>0)
{
// figure out how many of the selected messages need to be downloaded
for (sumNum=0;sumNum<(*tocH)->count;sumNum++)
if ((*tocH)->sums[sumNum].selected)
if (IMAPMessageBeingDownloaded(tocH, sumNum) || IMAPMessageDownloaded(tocH, sumNum)) c--;
// Make a handle big enough for them
uids = NuHandleClear(c*sizeof(unsigned long));
if (uids)
{
// and stick them in the handle
for (sumNum=0;sumNum<(*tocH)->count;sumNum++)
if ((*tocH)->sums[sumNum].selected)
if (!IMAPMessageBeingDownloaded(tocH, sumNum) && !IMAPMessageDownloaded(tocH, sumNum))
BMD(&((*tocH)->sums[sumNum].uidHash),&((unsigned long *)(*uids))[--c],sizeof(unsigned long));
// fetch the messages all in the background. Fetch completely if doing attachments.
UIDDownloadMessages(tocH, uids, false, attach);
}
else
{
WarnUser(MemError(), MEM_ERR);
}
}
}
/************************************************************************
* QueueMessFlagChange - remember a change to an IMAP message flag.
* Assume we're called if something's changed.
************************************************************************/
OSErr QueueMessFlagChange(TOCHandle tocH, short sumNum, StateEnum state, Boolean bTrashed)
{
OSErr err = noErr;
PersHandle pers = nil;
MailboxNodeHandle mb = nil;
LocalFlagChangeStruct flags;
long queuedFlagsSize = 0, oldFlagSize = 0;
Boolean done = false;
Boolean flagged = false;
short color = GetRLong(IMAP_FLAGGED_LABEL);
// must have been passed a TOC.
if (!tocH) return (paramErr);
// this must be an IMAP mailbox
if (!(*tocH)->imapTOC) return (paramErr);
// Is this message labeled as flagged?
if (color && (GetSumColor(tocH,sumNum) == color))
flagged = true;
// first, determine which mailbox this message belongs to.
mb = TOCToMbox(tocH);
pers = TOCToPers(tocH);
if (mb && pers)
{
//
// figure out what flags are to be set and reset.
//
Zero(flags);
flags.uid = (*tocH)->sums[sumNum].uidHash;
flags.mailbox = (*mb)->uidValidity;
flags.seen = ((*tocH)->sums[sumNum].state==READ)?1:0; // set seen flag if message is marked READ
flags.deleted = (((*tocH)->sums[sumNum].opts & OPT_DELETED)!=0)?1:0; // set deleted flag if message is marked for deleting locally
flags.flagged = flagged; // set flagged if the flagged label is used
flags.answered = ((*tocH)->sums[sumNum].state==REPLIED)?1:0; // set answered flag if message has been replied to
flags.draft = 0; // draft is currently unused
flags.recent = 0; // recent is set only when mail is delivered
if (flags.deleted && bTrashed) // transferring to the trash?
{
flags.deleted = 0;
flags.trashed = 1;
}
flags.processed = 0;
//
// stuff this flag change into the personalities flag change handle
//
LockMailboxNodeHandle(mb);
if (LockMailboxNodeFlags(mb))
{
// is this flag change already queued?
if ((*mb)->queuedFlags != nil)
{
short numChanges = 0;
LocalFlagChangePtr curFlags;
queuedFlagsSize = GetHandleSize((*mb)->queuedFlags);
numChanges = queuedFlagsSize / sizeof(LocalFlagChangeStruct);
while(numChanges>0)
{
curFlags = &((*((*mb)->queuedFlags))[numChanges-1]);
if (curFlags->uid == flags.uid)
{
BMD(&flags, curFlags, sizeof(LocalFlagChangeStruct));
done = true;
break;
}
numChanges--;
}
}
// must add this new flag change ...
if (!done)
{
// grow the queued flag handle
if ((*mb)->queuedFlags == nil)
{
queuedFlagsSize = sizeof(LocalFlagChangeStruct);
(*mb)->queuedFlags = NewZH(LocalFlagChangeStruct);
err = MemError();
}
else
{
oldFlagSize = GetHandleSize((*mb)->queuedFlags);
queuedFlagsSize = oldFlagSize + sizeof(LocalFlagChangeStruct);
SetHandleSize((Handle)((*mb)->queuedFlags), queuedFlagsSize);
err = MemError();
}
// save the flag change
if (((*mb)->queuedFlags) && (err==noErr))
BMD(&flags, &(((LocalFlagChangePtr)(*(*mb)->queuedFlags))[(oldFlagSize/sizeof(LocalFlagChangeStruct))]), sizeof(LocalFlagChangeStruct));
else
err = IMAPError(kIMAPQueueFlagChange, kIMAPMemErr, err);
}
UnlockMailboxNodeFlags(mb);
}
else
{
err = userCanceledErr;
}
UnlockMailboxNodeHandle(mb);
// mark this mailbox so we can recognize it when we process these new commands
if (err == noErr)
{
SetIMAPMailboxNeeds(mb, kNeedsExecCmd, true);
SetIMAPMailboxNeeds((*pers)->mailboxTree, kNeedsExecCmd, true);
}
}
else
err = IMAPError(kIMAPQueueFlagChange, kIMAPNotIMAPMailboxErr, errNotIMAPMailboxErr);
return (err);
}
/************************************************************************
* PerformQueuedOperations - perform all of the operations queued for
* this IMAP personality.
************************************************************************/
OSErr PerformQueuedCommands(PersHandle pers, IMAPStreamPtr imapStream, Boolean progress)
{
static Boolean bUnderway = false;
long ticks = TickCount();
Boolean progressed = false;
OSErr err = noErr;
// sanity
if (!imapStream || !pers || !IsIMAPPers(pers)) return noErr;
// no commands queued?
if (!DoesIMAPMailboxNeed((*pers)->mailboxTree, kNeedsExecCmd)) return noErr;
// is some other thread already doing this?
while (bUnderway && !CommandPeriod)
{
// put up a progress message if we've waiting for more than a second
if (!progressed && ((TickCount() - ticks)>60))
{
PROGRESS_MESSAGER(kpMessage,IMAP_WAITING_FOR_CONNECTION);
progressed = true;
}
CycleBalls();
if (MyYieldToAnyThread() != noErr) break;
}
// do nothing if the user cancelled
if (CommandPeriod) return (userCanceledErr);
// perform the queued commands
err = PerformQueuedCommandsLo(pers, (*pers)->mailboxTree, imapStream, progress);
// success?
if (err == noErr)
SetIMAPMailboxNeeds((*pers)->mailboxTree, kNeedsExecCmd, false);
return err;
}
/************************************************************************
* PerformQueuedCommandsLo - perform all of the operations queued for
* this IMAP personality.
************************************************************************/
OSErr PerformQueuedCommandsLo(PersHandle pers, MailboxNodeHandle tree, IMAPStreamPtr imapStream, Boolean progress)
{
OSErr err = noErr, retErr = noErr;
Str255 scratch;
// check each mailbox belonging to this personality
while (tree)
{
// are there queued flags waiting?
if ((*tree)->queuedFlags)
{
// SELECT the mailbox
if (IMAPOpenMailbox(imapStream, (*tree)->mailboxName,false))
{
// display some progress
if (progress)
{
ComposeRString(scratch,IMAP_QUEUED_COMMANDS,(*tree)->mailboxSpec.name);
PROGRESS_START;
PROGRESS_MESSAGE(kpSubTitle,scratch);
}
// update message flags ...
if ((*tree)->queuedFlags)
err = UpdateMessFlags(imapStream, tree, progress);
// perform queued transfers once supported ...
// success? Then don't check again.
if (err == noErr)
SetIMAPMailboxNeeds(tree, kNeedsExecCmd, false);
// write mailbox info back out to the file.
WriteIMAPMailboxInfo(&(*tree)->mailboxSpec, tree);
// return an error if any of this fails
retErr = retErr ? retErr : err;
}
}
// check the children
if ((*tree)->childList)
retErr |= PerformQueuedCommandsLo(pers, (*tree)->childList, imapStream, progress);
// next box
tree = (*tree)->next;
}
if (progress) PROGRESS_END;
// Any Undos we may have left around are probably stale ...
NukeXfUndo();
return (retErr);
}
/************************************************************************
* ExecuteAllPendingIMAPCommands - execute all pending IMAP commands
* for each personality that is set to do so on quit.
************************************************************************/
void ExecuteAllPendingIMAPCommands(void)
{
IMAPStreamPtr imapStream;
PushPers(CurPers);
for (CurPers=PersList;CurPers;CurPers=(*CurPers)->next)
{
if (IsIMAPPers(CurPers)
&& PrefIsSet(PREF_IMAP_EXECUTE_QUEUED_COMMANDS_ON_QUIT)
&& DoesIMAPMailboxNeed((*CurPers)->mailboxTree, kNeedsExecCmd))
{
// execute all pending IMAP commands for this pers and clean up immediately
imapStream = GetIMAPConnection(UndefinedTask, false);
if (imapStream) CleanupConnection(&imapStream);
}
}
PopPers();
}
/************************************************************************
* UpdateMessFlags - update the flags of a message in the IMAP mailbox
* we're currently connected to. This routine is slightly more
* efficient, as it attempts to chunk flag change operations together.
************************************************************************/
OSErr UpdateMessFlags(IMAPStreamPtr imapStream, MailboxNodeHandle mailbox, Boolean progress)
{
OSErr err = noErr;
long queuedFlagsSize = 0;
long numChanges = 0, totalFlags = 0, processedFlags = 0;
Accumulator uidsToChange;
LocalFlagChangePtr curFlags, scan;
short i;
Boolean bPendingTrash = false;
TOCHandle srcToc;
PersHandle pers;
MailboxNodeHandle trash = NULL;
TOCHandle hidTocH;
short sumNum;
// must be connected, authenticated, and selected
if (!imapStream || !IsSelected(imapStream->mailStream)) return (paramErr);
// must have been passed a personality, must have some queued flags
if (!mailbox || !((*mailbox)->queuedFlags)) return (noErr);
// lock the mailbox node. Don't let it go anywhere
LockMailboxNodeHandle(mailbox);
// lock the flags handle. Don't let anyone else mess with it
if (LockMailboxNodeFlags(mailbox))
{
// make sure there are still some flags to update. Another thread
// may have processed them while we were waiting for the lock.
if (((*mailbox)->queuedFlags!=nil) && ((queuedFlagsSize = GetHandleSize((*mailbox)->queuedFlags)) != 0))
{
// now update the flags
// don't let them go anywhere
LDRef((*mailbox)->queuedFlags);
// first update the flags
totalFlags = numChanges = queuedFlagsSize/sizeof(LocalFlagChangeStruct);
while(numChanges>0 && !CommandPeriod)
{
curFlags = &((LocalFlagChangePtr)(*((*mailbox)->queuedFlags)))[numChanges-1];
// remember if we see a message that needs to be trashed ...
bPendingTrash |= curFlags->trashed;
if (curFlags->processed == 0)
{
curFlags->processed = 1; // we won't consider this flag later
// add it to the list of flags to be processed this time through ...
if (AccuInit(&uidsToChange) == noErr)
{
AccuAddLong(&uidsToChange, curFlags->uid);
// find all flag changes that are similar. We can execute them at once.
for (i = 0; i < totalFlags; i++)
{
scan = &((LocalFlagChangePtr)(*((*mailbox)->queuedFlags)))[i];
if (scan->processed == 0) // this flag has not been processed yet
{
if (SameFlags(curFlags, scan)) // this flag change is the same
{
scan->processed = 1; // this flag is being processed now, too.
AccuAddLong(&uidsToChange, scan->uid);
}
}
}
AccuTrim(&uidsToChange);
// process the flag changes.
if (!ProcessSimilarFlagChanges(imapStream, mailbox, uidsToChange.data, curFlags, progress, totalFlags, &processedFlags))
err = paramErr;
AccuZap(uidsToChange);
}
}
numChanges--;
}
// the do the queued transfers to the Trash
if (bPendingTrash)
{
// find the toc we're trashing messages from
srcToc = TOCBySpec(&(*mailbox)->mailboxSpec);
// find the hidden toc, if it exists
hidTocH = GetHiddenCacheMailbox(mailbox, false, false);
// where is the trash?
pers = TOCToPers(srcToc);
if (pers)
trash = GetIMAPTrashMailbox(pers, true, true);
if (trash)
{
// build the list of UIDs to transfer to the trash.
numChanges = 0;
totalFlags = queuedFlagsSize/sizeof(LocalFlagChangeStruct);
for (i = 0; i < totalFlags; i++)
{
curFlags = &((LocalFlagChangePtr)(*((*mailbox)->queuedFlags)))[i];
if (curFlags->trashed)
numChanges++;
}
// are there any messages to transfer to the trash?
if (numChanges)
{
// make a list of uids to transfer ...
if (AccuInit(&uidsToChange) == noErr)
{
for (i = 0; i < totalFlags; i++)
{
curFlags = &((LocalFlagChangePtr)(*((*mailbox)->queuedFlags)))[i];
if (curFlags->trashed)
{
AccuAddLong(&uidsToChange, curFlags->uid);
// also, unmark the summary as deleted. It's not, it's just hidden
if (hidTocH)
{
sumNum = FindSumByHash(hidTocH, curFlags->uid);
if (sumNum >= 0)
(*hidTocH)->sums[sumNum].opts &= ~OPT_DELETED;
}
}
}
AccuTrim(&uidsToChange);
// copy messages to the trash mailbox then delete them from the source
if ((mailbox == trash) || CopyMessages(imapStream, trash, uidsToChange.data, true, true))
DoUIDMarkAsDeleted(imapStream, uidsToChange.data, false);
// if we deleted messages from the trash, expunge it immediately
if (mailbox == trash)
if (Expunge(imapStream))
UpdateLocalSummaries(srcToc, uidsToChange.data, true, true, false);
// cleanup
AccuZap(uidsToChange);
}
}
}
}
// Cleanup
UL((*mailbox)->queuedFlags);
ZapHandle((*mailbox)->queuedFlags);
(*mailbox)->queuedFlags = nil;
}
// we're done with the flags. Other people are free to muck with them
UnlockMailboxNodeFlags(mailbox);
}
// done with the mailbox as well
UnlockMailboxNodeHandle(mailbox);
return (err);
}
/**********************************************************************
* SameFlags - see if two flag change structures are "equal"
**********************************************************************/
Boolean SameFlags(LocalFlagChangePtr one, LocalFlagChangePtr two)
{
Boolean result = false;
if (one->mailbox == two->mailbox) // must be talking about the same mailbox
if (one->deleted == two->deleted)
if (one->seen == two->seen)
if (one->answered == two->answered)
if (one->draft == two->draft)
if (one->recent == two->recent)
result = true;
return (result);
}
/************************************************************************
* ProcessSimilarFlagChanges - execute the same flag changes over
* a range of messages
************************************************************************/
Boolean ProcessSimilarFlagChanges(IMAPStreamPtr imapStream, MailboxNodeHandle mailbox, Handle uidsToChange, LocalFlagChangePtr theFlags, Boolean progress, long totalFlags, long *processedFlags)
{
Boolean result = true;
short i, numFlags = GetHandleSize(uidsToChange)/sizeof(unsigned long);
long uid;
IMAPFLAGS flags;
Str255 uidStr;
long ticks, lastProgress = 0;
Str255 progressMessage;
Boolean bDeleteToo = !DoesIMAPMailboxNeed(mailbox, kShowDeleted);
// Sort the list of messages to be copied for maximum performance
if (!PrefIsSet(PREF_IMAP_DONT_USE_UID_RANGE)) SortUIDListHandle(uidsToChange);
for (i = 0; i < numFlags; i++)
{
// build the range string for the command ...
if (PrefIsSet(PREF_IMAP_DONT_USE_UID_RANGE))
{
uid = ((unsigned long *)(*uidsToChange))[i];
sprintf (uidStr,"%lu",uid);
}
else
i = GenerateUIDString(uidsToChange, i, &uidStr);
// display some progress
if (progress && ((ticks=TickCount()) - lastProgress > 60))
{
ComposeRString(progressMessage,IMAP_MARKING_MESSAGES, MIN(*processedFlags + i + 1, totalFlags), totalFlags);
PROGRESS_MESSAGE(kpMessage,progressMessage);
lastProgress = ticks;
}
// do the flag change
Zero(flags);
// turn off the flags to turn off ...
flags.SEEN = !theFlags->seen;
flags.ANSWERED = !theFlags->answered;
flags.FLAGGED = !theFlags->flagged;
if (bDeleteToo)
flags.DELETED = !theFlags->deleted;
if (flags.SEEN || flags.ANSWERED || flags.FLAGGED || flags.DELETED)
result = UIDSaveFlags(imapStream, 0, uidStr, &flags, false, true);
// turn on the flags to turn on ...
flags.SEEN = theFlags->seen;
flags.ANSWERED = theFlags->answered;
flags.FLAGGED = theFlags->flagged;
if (bDeleteToo)
flags.DELETED = theFlags->deleted;
if (flags.SEEN || flags.ANSWERED || flags.FLAGGED || flags.DELETED)
result &= UIDSaveFlags(imapStream, 0, uidStr, &flags, true, true);
}
*processedFlags += numFlags;
return (result);
}
/**********************************************************************
* LockMailboxNodeFlags - lock the flags before making changes to them
**********************************************************************/
Boolean LockMailboxNodeFlags(MailboxNodeHandle mailbox)
{
if (!mailbox) return (false);
// if already locked, wait for unlock
while (!CommandPeriod && (*mailbox)->flagLock)
{
CycleBalls();
if (MyYieldToAnyThread()) return(false);
}
(*mailbox)->flagLock = true;
return(true);
}
/**********************************************************************
* UnlockMailboxNodeFlags - unlock the flags when done changing them
**********************************************************************/
Boolean UnlockMailboxNodeFlags(MailboxNodeHandle mailbox)
{
(*mailbox)->flagLock = false;
return (true);
}
/**********************************************************************
* PendingMessFlagChange - is this message waiting for a flag update?
**********************************************************************/
Boolean PendingMessFlagChange(unsigned long uid, MailboxNodeHandle mailbox)
{
Boolean result = false;
long queuedFlagsSize;
long numChanges;
LocalFlagChangePtr curFlags;
if (!mailbox || !*mailbox) return (false);
if ((*mailbox)->queuedFlags)
{
queuedFlagsSize = GetHandleSize((*mailbox)->queuedFlags);
if (queuedFlagsSize > 0)
{
numChanges = queuedFlagsSize/sizeof(LocalFlagChangeStruct);
while (numChanges && !result)
{
curFlags = &((LocalFlagChangePtr)(*((*mailbox)->queuedFlags)))[numChanges-1];
if ((curFlags->mailbox == (*mailbox)->uidValidity) && (curFlags->uid == uid))
result = true;
numChanges--;
}
}
}
return (result);
}
/**********************************************************************
* FastIMAPMessageDelete - queue a delete request for a message
* and remove it from the toc immediately. Handle FTM as well.
*
* NOT THREAD SAFE
**********************************************************************/
Boolean FastIMAPMessageDelete(TOCHandle tocH, Handle uids, Boolean bFTM)
{
Boolean result = false;
MailboxNodeHandle mbox;
TOCHandle hidTocH;
short count, sumNum, numUids;
short lastSelected = -1;
// sanity check
if (!uids || !*uids || !tocH || !*tocH)
return false;
// must have at least one message to delete ...
if ((numUids = GetHandleSize(uids)/sizeof(unsigned long))==0)
return false;
//find the hidden toc ...
mbox = TOCToMbox(tocH);
hidTocH = GetHiddenCacheMailbox(mbox, false, true);
if (!hidTocH)
return false;
for (count=0;count<numUids;count++)
{
sumNum = UIDToSumNum(((unsigned long *)(*uids))[count], tocH);
if (sumNum < (*tocH)->count)
{
// Queue delete request.
// first mark the original message as deleted.
MarkSumAsDeleted(tocH, sumNum, true);
// do not filter this message ever again
(*tocH)->sums[sumNum].flags &= ~FLAG_UNFILTERED;
// queue the flag change
result |= (noErr == QueueMessFlagChange(tocH, sumNum, (*tocH)->sums[sumNum].state, bFTM));
// move the message to the hidden toc
HideShowSummary(tocH, tocH, hidTocH, sumNum);
// remember the last message that was deleted ...
lastSelected = sumNum;
}
}
// select the next message ...
if ((*tocH)->win)
BoxSelectAfter((*tocH)->win,lastSelected);
// add the undo if all went well ...
if (result)
AddIMAPXfUndoUIDs(tocH, hidTocH, uids,true);
return (result);
}
/**********************************************************************
* CleanUpResyncTaskErrors - clean up the task progress window before
* a resync operation gets underway
**********************************************************************/
void CleanUpResyncTaskErrors(MailboxNodeHandle mbox)
{
PersHandle pers;
if (mbox && (*mbox))
{
// who does this mailbox belong to?
pers = MailboxNodeToPers(mbox);
if (pers)
{
// remove any Resync related errors
RemoveTaskErrors(IMAPResyncTask,(*pers)->persId);
// remove any errors about multple resyncs
RemoveTaskErrors(IMAPMultResyncTask,(*pers)->persId);
// remove any checking mail errors if this was a mail check
if (mbox==LocateInboxForPers(pers)) RemoveTaskErrors(CheckingTask,(*pers)->persId);
}
}
}
/************************************************************************
* IMAPAlert - display the last ALERT message in the TP window
************************************************************************/
void IMAPAlert(IMAPStreamPtr stream, TaskKindEnum taskKind)
{
static TaskKindEnum lastTaskToAlert = UndefinedTask;
// if the user cancelled, don't display an error message
if (CommandPeriod) return;
// make sure there's an ALERT string to display
if (stream && stream->mailStream && stream->mailStream->alertStr[0])
{
// remember that this task was the last to display an ALERT message
lastTaskToAlert = taskKind;
if (!PrefIsSet(PREF_IGNORE_IMAP_ALERTS) && InAThread())
AddTaskErrorsS("",stream->mailStream->alertStr,IMAPAlertTask,(*CurPers)->persId);
// clear the alert string
stream->mailStream->alertStr[0] = 0;
}
else
{
// no ALERt to display. If this task is the same sort of task that ALERTed
// last time, remove the ALERT from the TP Window
if (taskKind == lastTaskToAlert)
RemoveTaskErrors(IMAPAlertTask,(*CurPers)->persId);
}
return;
}
/************************************************************************
* IMAPTransferLocalCache - transfer the local cache as possible via
* a UIDPLUS response.
*
* Note, the copied message will never be filtered again if it's moved
* back to the Inbox.
*
* A myriad of things can go wrong here, so give it our best shot. Since
* we're dealing with the local cache, that should suffice.
************************************************************************/
OSErr IMAPTransferLocalCache(TOCHandle fromTocH, MSumPtr pOrigSum, TOCHandle toTocH, long newUid, Boolean copy)
{
OSErr err = noErr;
long origSumNum, newSumNum;
MSumType newSum, *pNewSum;
Boolean bNoSaveCache = false;
// sanity check
if (!fromTocH || !toTocH || !pOrigSum)
return (paramErr);
// determine if we're transferring to a mailbox that's not interested in retaining the cache.
bNoSaveCache = PrefIsSet(PREF_DROP_TRASH_CACHE) && IsIMAPTrashMailbox(TOCToMbox(toTocH));
// locate original message
origSumNum = FindSumByHash(fromTocH, pOrigSum->uidHash);
// look in hidden toc if it's not found
if (origSumNum == -1)
{
fromTocH = GetHiddenCacheMailbox(TOCToMbox(fromTocH), false, false);
if (fromTocH) origSumNum = FindSumByHash(fromTocH, pOrigSum->uidHash);
}
if (origSumNum != -1)
{
// does this message already exist in the cache? Replace it.
if ((newSumNum = FindSumByHash(toTocH, newUid)) != -1)
{
CopySum(pOrigSum, &(*toTocH)->sums[newSumNum], 0);
(*toTocH)->sums[newSumNum].flags &= ~FLAG_UNFILTERED;
(*toTocH)->sums[newSumNum].uidHash = newUid; // restore the uid. -jdboud 12/16/03
InvalSum(toTocH,newSumNum);
}
else
{
// copy the summary and save the cache, if appropriate.
//
// Note, if the message being copied has even a single IMAP Stub file,
// don't copy the cache. If we did, we'd need to orphan the IMAP stub
// files, which are completely useless on their own and simplt take
// up disk space. Force the user to refetch the message instead, wiping
// out old stub files.
// is there some message to copy?
if (!bNoSaveCache && ((pOrigSum->opts&OPT_FETCH_ATTACHMENTS)==0) && (pOrigSum->offset!=imapNeedToDownload))
{
// AppendMessage uses the cache if it's around. Make sure's it's not,
// so the entire message is copied from the cache file on disk. (-39 error bug)
ZapHandle((*fromTocH)->sums[origSumNum].cache);
// copy the cached message to the new location
err = AppendMessage(fromTocH,origSumNum,toTocH,true,false,false);
if (err==noErr)
{
pNewSum = &(*toTocH)->sums[((*toTocH)->count)-1];
// set the UID to the new value
pNewSum->uidHash = newUid;
// grab the same attributes of the original summary
pNewSum->opts = pOrigSum->opts;
pNewSum->flags = pOrigSum->flags;
// reset the filter flag
pNewSum->flags &= ~FLAG_UNFILTERED;
// orphan the attachments from the new message if this was a copy.
// Removing it shouldn't remove the orignal message's attachments.
if (copy)
pNewSum->opts |= OPT_ORPHAN_ATT;
// orphan the attachments from the original message. This way, they
// won't be cleaned up if we delete the message later.
(*fromTocH)->sums[origSumNum].opts |= OPT_ORPHAN_ATT;
}
}
else
{
// Just copy the summary info
Zero(newSum);
CopySum(pOrigSum, &newSum, 0);
newSum.uidHash = newUid;
// clean up the summary.
newSum.offset = imapNeedToDownload;
newSum.bodyOffset = 0;
newSum.msgIdHash = 0;
newSum.subjId = 0;
newSum.mesgErrH = nil;
newSum.cache = nil;
newSum.messH = nil;
#ifdef NOBODY_SPECIAL
newSum.opts &= ~OPT_JUSTSUB;
#endif //NOBODY_SPECIAL
newSum.flags &= ~FLAG_UNFILTERED;
// put the adjusted summary into the mailbox
if (!SaveMessageSum(&newSum,&toTocH))
err = 1;
}
}
}
// else
// can't locate original message. Forgeddaboudid.
return (err);
}
/**********************************************************************
* IMAPMoveMessageDuringFiltering - transfer a message during filtering
**********************************************************************/
OSErr IMAPMoveMessageDuringFiltering(TOCHandle fromTocH, short sumNum, TOCHandle toTocH, Boolean copy, FilterPBPtr fpb)
{
OSErr err = noErr;
Boolean filteringUnderway = IMAPFilteringUnderway();
long uid;
uid = (*fromTocH)->sums[sumNum].uidHash;
// deselect the current message, and highlight the previous one.
(*fromTocH)->sums[sumNum].selected = false;
InvalSum(fromTocH,sumNum);
BoxSelectAfter((*fromTocH)->win,sumNum);
// Filtering takes place in the foreground. Free up the connection for the upcoming transfer
if (filteringUnderway) IMAPStopFiltering(false);
// IMAP to IMAP transfer
if ((toTocH && (*toTocH)->imapTOC) && ((*fromTocH)->imapTOC))
{
// copy the message from this mailbox to the destination
err = IMAPTransferMessage(fromTocH,toTocH,uid,true,true);
if (err==noErr)
{
// the destination mailbox has new mail if the transferred message was unread
if ((*fromTocH)->sums[sumNum].state==UNREAD) IMAPMailboxHasUnread(toTocH, true);
// then mark it as deleted. We'll expunge when filtering is complete
if (!copy)
{
IMAPMarkMessageAsDeleted(fromTocH, uid, false);
if (FancyTrashForThisPers(fromTocH)) FlagForExpunge(fromTocH);
}
}
}
// IMAP to POP transfer
else if ((toTocH && !(*toTocH)->imapTOC) && ((*fromTocH)->imapTOC))
{
// copy this message to the local mailbox
err = MoveMessageLo(fromTocH,sumNum,&(*toTocH)->mailbox.spec,true,false,true);
// turn off notifications for this message if a translator deleted it
if (fpb && ((*fromTocH)->sums[sumNum].opts&OPT_EMSR_DELETE_REQUESTED))
{
fpb->openMailbox = fpb->openMessage = fpb->print = false;
fpb->dontReport = fpb->dontUser = true;
}
// then mark it as deleted. We'll expunge when filtering is complete
if ((err==noErr) && !copy)
{
IMAPMarkMessageAsDeleted(fromTocH, uid, false);
if (FancyTrashForThisPers(fromTocH)) FlagForExpunge(fromTocH);
}
}
// POP to IMAP transfer
else if ((toTocH && (*toTocH)->imapTOC) && !((*fromTocH)->imapTOC))
{
// transfer the message to the IMAP mailbox
err = IMAPTransferMessageToServer(fromTocH,toTocH,sumNum, copy, true);
}
// go back to filtering
if (filteringUnderway) IMAPStartFiltering(fromTocH, true);
return (err);
}
/**********************************************************************
* CacheIMAPMessageForSpamWatch - cache as much of the given message
* as we can. Simply cache the message if it's downloaded. If it's
* not, fill the local memory cache with the headers of the message.
*
* Note, if we really wanted to, we could fetch parts of the message
* body as well for better Bayesian filtering. Someday, maybe.
**********************************************************************/
OSErr CacheIMAPMessageForSpamWatch(TOCHandle tocH, short sumNum)
{
Boolean filteringUnderway; // to interact better with IMAP filtering
Handle cache;
// Sanity check
if (!tocH || !*tocH // must have a toc
|| !(*tocH)->imapTOC // it must be an IMAP toc
|| (sumNum > (*tocH)->count)) // and the sumnum must be valid
return (paramErr);
// minimal header present
if ((*tocH)->sums[sumNum].offset == imapNeedToDownload)
{
// if this is an IMAP minimal header, go fetch the headers from the server.
filteringUnderway = IMAPFilteringUnderway();
if (!filteringUnderway) IMAPStartFiltering(tocH, true);
cache = IMAPFetchMessageHeadersForFiltering(tocH, sumNum);
HPurge(cache);
if (!filteringUnderway) IMAPStopFiltering(true);
ZapHandle((*tocH)->sums[sumNum].cache);
(*tocH)->sums[sumNum].cache = cache;
}
// full message already downloaded
else
{
CacheMessage (tocH, sumNum);
}
return (noErr);
}
/************************************************************************
* IMAPWarnings - display pending IMAP warnings once threads complete.
* UIDPLUS and junk warnings, for example.
************************************************************************/
void IMAPWarnings(void)
{
PersHandle pers;
if (gbDisplayIMAPWarnings && !GetNumBackgroundThreads())
{
gbDisplayIMAPWarnings = false;
for (pers = PersList; pers; pers = (*pers)->next)
{
if (IsIMAPPers(pers))
{
PushPers(pers);
CurPers = pers;
//
// "No SpamWatch because of lacking IMAP support" warning?
//
// Warn if IMAP support is lacking
// AND
// we haven't warned before
//
if (!JunkPrefHasIMAPSupport()
&& !JunkPrefNoIMAPSupportWarning())
{
// Warn the user that SpamWatch has been disabled.
ComposeStdAlert(Note,JUNK_PREFNOIMAP_WARNING,(*pers)->name);
// Don't warn again.
SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefNoIMAPSupportWarning);
// But do warn about support that appears in the future
SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)&~bJunkPrefNewIMAPSupportWarning);
}
//
// "IMAP Support for SpamWatch is available" warning?
//
// Warn if UIDPLUS is available
// AND we warned about lacking UIDPLUS sipport in the past
// AND we haven't warned about the new support.
//
if (!PrefIsSet(PREF_NO_USE_UIDPLUS) && JunkPrefHasIMAPSupport()
&& JunkPrefNoIMAPSupportWarning()
&& !JunkPrefNewIMAPSupportWarning())
{
// Warn the user that SpamWatch has been re-enabled
ComposeStdAlert(Note,JUNK_PREFIMAPAVAIL_WARNING,(*pers)->name);
// Don't warn again. Ever. Even if it appears to go away.
SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefNewIMAPSupportWarning);
SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefNoIMAPSupportWarning);
}
PopPers();
}
}
}
}
/************************************************************************
* IMAPSpamWatchSupported - enable or disable SpamWatch
************************************************************************/
void IMAPSpamWatchSupported(Boolean bSupported, Boolean bWarnIfNeeded)
{
//
// it's not supported, and we haven't warned before
//
if (!bSupported && !JunkPrefNoIMAPSupportWarning())
{
// turn off SpamWatch plugins
SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefIMAPNoRunPlugins);
gbDisplayIMAPWarnings = bWarnIfNeeded;
}
//
// it is supported, and we've not yet noted that
//
else if (bSupported && !JunkPrefHasIMAPSupport() && !PrefIsSet(PREF_NO_USE_UIDPLUS))
{
// remember that we (now) have IMAP support
SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefHasIMAPSupport);
if (JunkPrefNoIMAPSupportWarning())
{
// We've warned about lacking IMAP Support in the past. Warn about the new support.
SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)&~bJunkPrefIMAPNoRunPlugins);
gbDisplayIMAPWarnings = bWarnIfNeeded;
}
else
{
// we haven't warned in the past. Never warn if UIDPLUS support appears to go away.
SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefNoIMAPSupportWarning);
SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefNewIMAPSupportWarning);
}
}
}
/************************************************************************
* IMAPResetSpamSupportPrefs - reset all preferences related to support
* of SpamWatch on IMAP
************************************************************************/
void IMAPResetSpamSupportPrefs(void)
{
SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)&~bJunkPrefHasIMAPSupport);
SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)&~bJunkPrefNoIMAPSupportWarning);
SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)&~bJunkPrefNewIMAPSupportWarning);
}
/**********************************************************************
* IMAPPoll - poll for new messages
**********************************************************************/
void IMAPPoll(PersHandle pers)
{
threadDataHandle index;
// Something bad happened. We weren't told which mailbox to personality to poll.
if (!pers) return;
// we're Offline. Don't do anything.
if (Offline) return;
// do nothing if there's already a polling operation underway
for (index=gThreadData;index;index=(*index)->next)
if (((*index)->imapInfo.command == IMAPPollingTask) && ((*index)->imapInfo.targetBox == (*pers)->mailboxTree))
return;
// remove any old task errors
RemoveTaskErrors(IMAPPollingTask,(*pers)->persId);
// poll away
IMAPPollMailboxes((*pers)->mailboxTree);
}
/************************************************************************
* IMAPPollMailboxes - poll the mailboxes of a personality for new mail
************************************************************************/
void IMAPPollMailboxes(MailboxNodeHandle tree)
{
IMAPStreamPtr imapStream = nil;
Str255 s;
PersHandle pers = FindPersById((*tree)->persId);
long numToPoll, remaining;
// how many boxes must we poll?
numToPoll = remaining = IMAPCountMailboxes(tree, kNeedsPoll);
if (numToPoll)
{
PROGRESS_START;
ComposeRString(s,IMAP_POLLING_MAILBOXES,(*pers)->name);
PROGRESS_MESSAGE(kpTitle,s);
// open an imap stream to the server.
imapStream = GetIMAPConnection(IMAPPollingTask, CAN_PROGRESS);
if (imapStream)
{
IMAPPollMailboxTree(imapStream, tree, numToPoll, &remaining);
CleanupConnection(&imapStream);
}
PROGRESS_END;
}
}
/************************************************************************
* IMAPPollMailboxTree - check a tree for new messages
************************************************************************/
void IMAPPollMailboxTree(IMAPStreamPtr imapStream, MailboxNodeHandle tree, long numToPoll, long *remaining)
{
short numMessages;
TOCHandle tocH, hidTocH;
Str63 m;
Str255 s;
WindowPtr behindWP = OpenBehindMePlease();
MailboxNodeHandle inbox = LocateInboxForPers(CurPers);
while (tree)
{
// stop if command period was hit
if (CommandPeriod)
return;
LockMailboxNodeHandle(tree);
// is this an IMAP mailbox that can contain messages?
if (!((*tree)->attributes & LATT_NOSELECT) && !((*tree)->attributes & LATT_ROOT))
{
// does it need to be polled?
if (DoesIMAPMailboxNeed(tree, kNeedsPoll))
{
// display some progress indicating which mailbox we're polling
PathToMailboxName((*tree)->mailboxName, m, (*tree)->delimiter);
ComposeRString(s,IMAP_POLL_MAILBOX,m);
PROGRESS_MESSAGE(kpSubTitle,s);
BYTE_PROGRESS(nil,numToPoll-(*remaining),numToPoll);
(*remaining)--;
// how many messages are in the local mailbox cache?
tocH = TOCBySpec(&(*tree)->mailboxSpec);
if (tocH)
{
// make sure this toc doesn't go anywhere.
IMAPTocHBusy(tocH, true);
// is it not currently being synched or scheduled to be?
if (!DoesIMAPMailboxNeed(tree, kNeedsResync))
{
numMessages = (*tocH)->count;
// check the hidden toc, too
if ((hidTocH=GetHiddenCacheMailbox(tree, false, false))!=NULL)
numMessages += (*hidTocH)->count;
// how many messages does the server think are in the mailbox?
if (IMAPOpenMailbox(imapStream, (*tree)->mailboxName,false))
{
// are there more than the local cache knows about?
if(GetMessageCount(imapStream) != numMessages)
{
// fetch new messages in this mailbox.
FetchNewMessages(tocH, true, true, false, true);
// Open the mailbox if the preference is set.
if (!PrefIsSet(PREF_NO_OPEN_IN))
{
ShowBoxAt(tocH,(*tocH)->previewPTE ? -1:FumLub(tocH),behindWP);
if (PrefIsSet(PREF_ZOOM_OPEN)) ReZoomMyWindow(GetMyWindowWindowPtr((*tocH)->win));
behindWP = GetMyWindowWindowPtr((*tocH)->win);
}
}
}
}
// don't need the toc anymore
IMAPTocHBusy(tocH, false);
}
}
}
// check this node's children
if ((*tree)->childList) IMAPPollMailboxTree(imapStream, (*tree)->childList, numToPoll, remaining);
UnlockMailboxNodeHandle(tree);
// check the next node.
tree = (*tree)->next;
}
}
/**********************************************************************
* Code to prevent virus scanners and other stupid software from
* screwing us up when we're decoding IMAP messages.
*
* IMAP downloads its messages to a temp file in the spool folder.
* During idle time, Eudora goes through the Spool folder looking
* for these temp files. When one is found, the messages are decoded
* and placed in the proper IMAP mailbox. The temp file is then erased
*
* Now the bad part: certain virus scanners like Norton AnitVirus
* look for viruses in messages and scan the spool folder. In some
* cases, the scanner may open the file as read only before Eudora
* gets a chance to delete it. If that happens, we'll be unable to
* delete the file and the messages may be copied into the mailbox
* many times.
*
* The correct solution is to turn off you virus software. The last
* virus I saw was 'WDEF' in 1987 and it didn't even arrive via email.
*
* As we're in the business of offering alternative solutions, the
* code below will work around the problem with minimal impact on the
* folks out there smart enough not to get into this predicament in
* the first place:
*
* Once an IMAP message temp file is processed, MarkAsProcessed()
* marks the file as processed. In the idle loop looking for temp
* files, look for the processed resource with HasBeenProcessed().
* If it's there, just toss the file.
**********************************************************************/
/**********************************************************************
* MarkAsProcessed - set the label of this temp file so we know it's
* been processed.
**********************************************************************/
void MarkAsProcessed(FSSpec *spec)
{
// set the label of this file so we know it's been processed.
FixSpecUnread(spec,true);
}
/**********************************************************************
* HasBeenProcessed - returns true if the messages in this temp
* file have already been prceesed.
**********************************************************************/
Boolean HasBeenProcessed(FSSpec *spec)
{
Boolean bHas = false;
OSErr err = noErr;
CInfoPBRec mailboxFileInfo;
// Find the mailbox cache file on disk ...
Zero(mailboxFileInfo);
err = HGetCatInfo(spec->vRefNum,spec->parID,spec->name,&mailboxFileInfo);
if (err == noErr)
bHas = (mailboxFileInfo.hFileInfo.ioFlFndrInfo.fdFlags&0xe) != 0;
return (bHas);
}
/**********************************************************************
* Return TRUE when it's all right to quit. Process pending IMAP
* changes while we're waiting.
**********************************************************************/
Boolean IMAPDoQuit(void)
{
Boolean bCanQuit = true;
TOCHandle tocH;
MailboxNodeHandle mBox;
// first off, cancel filtering. We can resume that when Eudora next starts up.
gFilteringCancelled = true;
// next, wait for all background threads to complete
if (GetNumBackgroundThreads()) return (false);
// finally, perform all waiting mailbox updates
for (tocH=TOCList; tocH; tocH = (*tocH)->next)
{
// is this an IMAP mailbox?
mBox = TOCToMbox(tocH);
if (mBox)
{
// is this mailbox waiting for updates?
if ((*mBox)->tocRef)
{
// perform them.
UpdateIMAPMailbox(tocH);
// we can't quit yet.
bCanQuit = false;
}
else
{
// forget about filtering and resyncs. Perform those later.
SetIMAPMailboxNeeds(mBox, kNeedsFilter, false);
SetIMAPMailboxNeeds(mBox, kNeedsResync, false);
}
}
}
// we're done! We can quit.
return (bCanQuit);
}
#ifdef DEBUG
/**********************************************************************
* IMAPCollectFlags() - fetch flags for all IMAP mailboxes, allow cancel
**********************************************************************/
void IMAPCollectFlags(void)
{
IMAPStreamPtr imapStream = nil;
PersHandle oldPers = CurPers;
SignedByte state;
Str255 p;
short flagsFileRefN = OpenFlagsFile();
if (flagsFileRefN > 0)
{
OpenProgress();
ProgressMessage(kpTitle,"\pCollecting flags from mailboxes");
for (CurPers = PersList; CurPers && !CommandPeriod; CurPers = (*CurPers)->next)
{
if (MailboxTreeGood(CurPers))
{
ComposeString(p, "\p%p", (*CurPers)->name);
PROGRESS_MESSAGE(kpSubTitle,p);
imapStream = GetIMAPConnection(IMAPResyncTask, CAN_PROGRESS);
if (imapStream)
{
state = HGetState((Handle)CurPers);
LDRef(CurPers);
IMAPCollectFlagsFromTree(imapStream,(*CurPers)->mailboxTree, flagsFileRefN);
HSetState(CurPers, state);
}
CleanupConnection(&imapStream);
}
}
CurPers = oldPers;
CloseProgress();
MyFSClose(flagsFileRefN);
}
}
/**********************************************************************
* IMAPCollectFlagsFromTree() - fetch flags for all IMAP mailboxes in
* the tree
**********************************************************************/
void IMAPCollectFlagsFromTree(IMAPStreamPtr imapStream, MailboxNodeHandle *tree, short refN)
{
MailboxNodeHandle scan = tree;
Str255 p;
while (scan && !CommandPeriod)
{
LockMailboxNodeHandle(scan);
ComposeString(p, "\pScanning %p.", (*scan)->mailboxSpec.name);
ProgressMessage(kpSubTitle,p);
// open the mailbox on the server
if (IMAPOpenMailbox(imapStream, (*scan)->mailboxName,false))
{
// open a file to dump the flags into
imapStream->mailStream->flagsRefN = refN;
// fetch all the flags
UIDFetchFlags(imapStream, nil);
// ignore the file.
imapStream->mailStream->flagsRefN = -1;
}
if ((*scan)->childList) IMAPCollectFlagsFromTree(imapStream, (*scan)->childList, refN);
UnlockMailboxNodeHandle(scan);
scan = (*scan)->next;
}
}
/************************************************************************
* OpenMessDestFile - create and open a temp file for a mailbox.
* We'll close the file when the imapStream is zapped.
************************************************************************/
short OpenFlagsFile(void)
{
short ref = -1;
OSErr err = noErr;
FSSpec flagsSpec;
const unsigned char *flagFileName = "\pIMAPFlagUsage";
Str255 ctext;
long creator;
// create (or open) the spool file in the Eudora folder.
err = FSMakeFSSpec(Root.vRef,Root.dirId,flagFileName,&flagsSpec);
if (err == fnfErr)
{
// not found in root folder. Create the file.
GetPref(ctext,PREF_CREATOR);
if (*ctext!=4) GetRString(ctext,TEXT_CREATOR);
BMD(ctext+1,&creator,4);
err = HCreate(flagsSpec.vRefNum,flagsSpec.parID,flagsSpec.name,creator,'TEXT');
}
// open the file
if (err == noErr) err = AFSHOpen(flagsSpec.name,flagsSpec.vRefNum,flagsSpec.parID,&ref,fsRdWrPerm);
if ((err != noErr) || (ref == -1))
{
WarnUser(NO_TEMP_FILE, err);
ref = -1;
}
else SetFPos(ref,fsFromLEOF,0);
return (ref);
}
#endif
#endif //IMAP