/* 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 * .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;countcount;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;isums[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 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;countcount;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;(countsums[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; isums[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; imailboxName); 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; (ifinished = 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;counttd)))[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;countcount;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;lBeginisDirty = 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;countcount) { // 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