/* 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. */ #include "junk.h" #define FILE_NUM 144 #include "trans.h" typedef enum { kAutoScore, kUserMarkJunk, kUserMarkNotJunk } TJunkType; void ScoreOneMessage ( TLMHandle tList, TOCHandle tocH, short sumNum, TJunkType howToScore ); Boolean NagHitReturnItem(EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon); void MarkOneAsJunk ( TLMHandle tList, TOCHandle tocH, short sumNum, Boolean isJunk ); OSErr JunkSetScoreLo(TOCHandle tocH,short sumNum,short because,short score); Boolean CanScoreToc( TOCHandle tocH ); Boolean WhiteListByMID(TOCHandle tocH, short sumNum); // IMAP support OSErr JunkIMAP(TOCHandle tocH, short sumNum, Boolean isJunk, Boolean dontMove); OSErr ArchiveIMAPJunk(void); void JunkMoveIMAPMessages(TOCHandle tocH, Boolean isJunk); /************************************************************************ * MoveToJunk - ************************************************************************/ OSErr MoveToJunk ( TOCHandle source, short spamThresh, FilterPB *fpb ) { OSErr err = noErr; TOCHandle junk = GetJunkTOC (); short found = 0; int i; if ( junk == NULL ) return paramErr; // select all messages above the junk mailbox threshhold for ( i = 0; i < (*source)->count; i++ ) { if ((*source)->sums [ i ].spamScore < spamThresh ) BoxSetSummarySelected ( source, i, false ); else { BoxSetSummarySelected ( source, i, true ); found += 1; // don't trust the date on junk if ( !JunkPrefBelieveDate ()) (*source)->sums[i].seconds = (*source)->sums[i].arrivalSeconds; // Clean up the server if ( JunkPrefServerDel() && !((*source)->sums[i].flags & FLAG_SKIPPED )) AddTSToPOPD ( DELETE_ID, source, i, False ); } } // if we found any junk, move it to the junk mailbox if ( found > 0 ) { FSSpec junkSpec = GetMailboxSpec ( junk, -1 ); err = MoveSelectedMessagesLo ( source, &junkSpec, false, false, false, false ); // Bookkeeping if ( PrefIsSet ( PREF_REPORT ) && fpb != NULL ) while ( found-- > 0 ) AddSpecToList ( &junkSpec, fpb->report ); UseFeature ( featureJunk ); } return err; } /********************************************************************** * MoveToIMAPJunk - if an IMAP message is junk, get rid of it. * * Note, this is different than MoveToJunk that is used on local * mailboxes. It must be able to move messages one at a time for * incremental IMAP filtering. * * Additionally, it does not move open spam messages, nor does it * move deleted messages. **********************************************************************/ OSErr MoveToIMAPJunk(TOCHandle source, short sumNum, short spamThresh, FilterPB *fpb) { OSErr err = noErr; TOCHandle junkTocH = GetJunkTOC (); short found = 0; short i; short start, end; Handle uids; FSSpec junkSpec; // locate the proper junk mailbox. Don't warn the user, we're already // warned them before the IMAP operation got underway. junkTocH = LocateIMAPJunkToc(source, true, true); // Sanity check if (((*source)->imapTOC == 0) || (junkTocH == NULL)) return (paramErr); // count the messages to be moved if (sumNum == -1) { // do them all. start = 0; end = (*source)->count; // count number of spam messages. Ignore deleted and open messages. for (i=start;(isums[i].spamScore >= spamThresh) && ((*source)->sums[i].messH == NULL) && (((*source)->sums[i].opts&OPT_DELETED)==0)) found++; } else { // just one message found = 1; } if (sumNum != -1) { // doing a single message. err = IMAPMoveMessageDuringFiltering(source, sumNum, junkTocH, false, fpb); } else { // doing a range of messages // junk all spam messages if (found > 0) { uids = NuHandleClear(found*sizeof(unsigned long)); if (uids) { i = found; for (sumNum=start;(sumNumsums[sumNum].spamScore >= spamThresh) && ((*source)->sums[sumNum].messH == NULL) && (((*source)->sums[sumNum].opts&OPT_DELETED)==0)) { ((unsigned long *)(*uids))[--i] = ((*source)->sums[sumNum].uidHash); // don't trust the date on junk if (!JunkPrefBelieveDate ()) (*source)->sums[sumNum].seconds = (*source)->sums[sumNum].arrivalSeconds; } err = IMAPTransferMessages(source, junkTocH, uids, false, false); } } // Bookkeeping if (PrefIsSet(PREF_REPORT) && (fpb != NULL)) { junkSpec = GetMailboxSpec (junkTocH, -1); while ( found-- > 0 ) AddSpecToList (&junkSpec, fpb->report); } UseFeature ( featureJunk ); } return err; } /********************************************************************** * FilterJunk - if a message is junk, get rid of it **********************************************************************/ OSErr FilterJunk ( TOCHandle fromTocH ) { OSErr err = noErr; JunkScoreBox ( fromTocH, -1, -1, true ); err = MoveToJunk ( fromTocH, GetRLong ( JUNK_MAILBOX_THRESHHOLD ), NULL ); return err; } // Helper routine for "Junk" - Mark one message as junk void MarkOneAsJunk ( TLMHandle tList, TOCHandle tocH, short sumNum, Boolean isJunk ) { short spamScore = isJunk ? GetRLong(JUNK_XFER_SCORE) : 0; if ( tList != NULL ) ScoreOneMessage ( tList, tocH, sumNum, isJunk ? kUserMarkJunk : kUserMarkNotJunk ); JunkSetScore ( tocH, sumNum, JUNK_BECAUSE_USER, !isJunk||spamScore?spamScore:(*tocH)->sums[sumNum].spamScore ); // don't trust the date on junk if (isJunk && !JunkPrefBelieveDate ()) (*tocH)->sums[ sumNum ].seconds = (*tocH)->sums[ sumNum ].arrivalSeconds; if (!(*tocH)->imapTOC && JunkPrefServerDel() && !((*tocH)->sums[ sumNum ].flags&FLAG_SKIPPED)) AddTSToPOPD( DELETE_ID, tocH, sumNum, False ); } /************************************************************************ * Junk - mark selected messages as junk (or not) ************************************************************************/ // no error handling yet // no easyopen yet OSErr Junk( TOCHandle tocH, short sumNum, Boolean isJunk, Boolean ezOpen ) { FilterPB fpb; FSSpec junkSpec; TOCHandle junkTocH; TLMHandle tList = NULL; short i; TOCHandle realTOC; short realSum; UseFeature(featureJunk); // Properly handle IMAP mailboxes ... if ((*tocH)->imapTOC) return (JunkIMAP(tocH, sumNum, isJunk, false)); // Porperly handle IMAP messages in virtual TOCs ... if ((*tocH)->virtualTOC) { // prrocess all IMAP messages in this virtual toc for (i=(*tocH)->count;i--;) if ((*tocH)->sums[i].selected) { realTOC = GetRealTOC(tocH,i,&realSum); if (realTOC) JunkIMAP(realTOC, realSum, isJunk, true); } // move all the processed IMAP messages JunkMoveIMAPMessages(tocH, isJunk); } (void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL ); // Looking for selected messages if ( sumNum < 0 ) { // mark the selected ones with the chosen score for (i=(*tocH)->count;i--;) if ((*tocH)->sums[i].selected ) { MarkOneAsJunk ( tList, tocH, i, isJunk ); if (!isJunk) { // redate if need be if ((*tocH)->sums[i].seconds==(*tocH)->sums[i].arrivalSeconds) RedateTS(tocH,i); // put back on server if need be PushPers(PERS_FORCE(TS_TO_PERS(tocH,i))); if (PrefIsSet(PREF_LMOS)) RemIdFromPOPD(PERS_POPD_TYPE(CurPers),DELETE_ID,(*tocH)->sums[i].uidHash); PopPers(); } } // if they're being marked not junk... if (!isJunk) { Boolean filter = true; // Whitelist them? if (JunkPrefNotJunkWhite()) WhiteListTS(tocH,-1); // Before filtering, adjust the selection to only messages // actually in the junk mailbox, if it's a virtual TOC if ((*tocH)->virtualTOC) for (i=(*tocH)->count-1;i>=0;i--) { FSSpec spec; if ((*tocH)->sums[sumNum].selected) { if ((*tocH)->sums[sumNum].flags&FLAG_OUT) BoxSetSummarySelected(tocH,i,false); else { spec = GetMailboxSpec(tocH,i); if (!SpecIsJunkSpec(&spec)) BoxSetSummarySelected(tocH,i,false); } } } else if (!BoxIsJunkBox(tocH)) filter = false; if (filter) { // filter them FilterSelectedMessages(flkIncoming,tocH,&fpb); FilterPostprocess(flkIncoming,&fpb); } } // if they're being marked junk, transfer them to junk else if (!BoxIsJunkBox(tocH)) { if ((*tocH)->which!=JUNK && (junkTocH=GetJunkTOC())) { junkSpec = GetMailboxSpec(junkTocH,-1); MoveSelectedMessages(tocH,&junkSpec,false); } } } else { // do just the one message MarkOneAsJunk ( tList, tocH, sumNum, isJunk ); // if it's not junk... if (!isJunk) { if (!isJunk) { // fix the date? if ((*tocH)->sums[sumNum].seconds==(*tocH)->sums[sumNum].arrivalSeconds) RedateTS(tocH,sumNum); // put back on server if need be PushPers(PERS_FORCE(TS_TO_PERS(tocH,sumNum))); if (PrefIsSet(PREF_LMOS)) RemIdFromPOPD(PERS_POPD_TYPE(CurPers),DELETE_ID,(*tocH)->sums[sumNum].uidHash); PopPers(); } // Whitelist it? if (JunkPrefNotJunkWhite()) WhiteListTS(tocH,sumNum); // Filter it FilterMessage(flkIncoming,tocH,sumNum); } // if it is junk, move it else if (ezOpen) TransferMenuChoice(TRANSFER_MENU,MAILBOX_JUNK_ITEM,tocH,sumNum,0,false); else if ((*tocH)->which!=JUNK && (junkTocH=GetJunkTOC())) { junkSpec = GetMailboxSpec(junkTocH,-1); MoveMessageLo(tocH,sumNum,&junkSpec,false,false,true); } } // lies, lies, lies if ( tList != NULL ) DisposeHandle ( tList ); return noErr; } /************************************************************************ * JunkIMAP - mark selected IMAP messages as junk (or not) ************************************************************************/ OSErr JunkIMAP(TOCHandle tocH, short sumNum, Boolean isJunk, Boolean dontMove) { OSErr err = noErr; TLMHandle tList = NULL; TOCHandle destTocH = NULL; PersHandle imapPers = TOCToPers(tocH); MailboxNodeHandle imapDest = NULL; Boolean filteringUnderway = IMAPFilteringUnderway(); long uid = (*tocH)->sums[sumNum].uidHash; short i, count; Handle uids; // sanity check if (!tocH || !imapPers) return (paramErr); // build list of translators ... (void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL ); // Looking for selected messages if ( sumNum < 0 ) { // mark the selected ones with the chosen score for (i=(*tocH)->count;i--;) if ((*tocH)->sums[i].selected ) MarkOneAsJunk (tList, tocH, i, isJunk); // determine where to move them if (!isJunk) { // Whitelist them? if (JunkPrefNotJunkWhite()) WhiteListTS(tocH,-1); if (BoxIsJunkBox(tocH)) { // Put the message back into the proper inbox. if ((imapDest = LocateInboxForPers(imapPers)) != NULL) destTocH = TOCBySpec(&(*imapDest)->mailboxSpec); } } // if they're being marked junk, transfer them to junk else if (!BoxIsJunkBox(tocH)) { // This is an IMAP mailbox we're junking from. Get the proper JUNK mailbox imapDest = GetIMAPJunkMailbox(imapPers, true, false); if (imapDest && (imapDest != TOCToMbox(tocH))) destTocH = TOCBySpec(&(*imapDest)->mailboxSpec); } if (!dontMove) { // build a list of the currently selected, non-deleted messages and move them. // if we're not junking, the selection may have expanded! count = 0; for (i=0;(i<(*tocH)->count);i++) if (((*tocH)->sums[i].selected) && (((*tocH)->sums[i].opts&OPT_DELETED)==0)) count++; if (count) { uids = NuHandleClear(count*sizeof(unsigned long)); if (uids) { for (i=0;i<(*tocH)->count && count;i++) if (((*tocH)->sums[i].selected) && (((*tocH)->sums[i].opts&OPT_DELETED)==0)) { ((unsigned long *)(*uids))[--count] = ((*tocH)->sums[i].uidHash); // also mark the messages as not junk to catch the expanded selection if (!isJunk) MarkOneAsJunk (tList, tocH, i, false); } // actually move them now if (destTocH) err = IMAPTransferMessages(tocH, destTocH, uids, false, false); } else { // Memory error. DisposeHandle (tList); return (MemError()); } } } } else { // do just the one message MarkOneAsJunk (tList, tocH, sumNum, isJunk); if (!dontMove) { // if it's not junk... if (!isJunk) { // Whitelist it? if (JunkPrefNotJunkWhite()) WhiteListTS(tocH,sumNum); // Find the proper Inbox to move this messgae back to if ((imapDest = LocateInboxForPers(imapPers)) != NULL) destTocH = TOCBySpec(&(*imapDest)->mailboxSpec); // Move the message if (destTocH) err = IMAPTransferMessage(tocH, destTocH, uid, false, false); } else if (!BoxIsJunkBox(tocH)) { imapDest = GetIMAPJunkMailbox(imapPers, true, false); if (imapDest) { if ((destTocH = TOCBySpec(&(*imapDest)->mailboxSpec)) != NULL) { // this gets a little tricky with IMAP filtering ... if (filteringUnderway) { IMAPStopFiltering(false); // copy the message from this mailbox to the destination if (IMAPTransferMessage(tocH,destTocH,uid,true,true)==noErr) { // then mark it as deleted. We'll expunge when filtering is complete IMAPMarkMessageAsDeleted(tocH, uid, false); if (FancyTrashForThisPers(tocH)) FlagForExpunge(tocH); } IMAPStartFiltering(tocH, true); } else { MoveMessageLo(tocH,sumNum,&(*imapDest)->mailboxSpec,false,false,true); } } } } } } // lies, lies, lies if ( tList != NULL ) DisposeHandle ( tList ); return noErr; } /************************************************************************ * ArchiveJunk - destroy the junk that's there ************************************************************************/ OSErr ArchiveJunk(TOCHandle tocH) { long i; uLong threshTime = GMTDateTime() - GetRLong(JUNK_MAILBOX_EMPTY_DAYS) * 24 * 3600; uLong threshScore = GetRLong(JUNK_MAILBOX_EMPTY_THRESH); long count = 0; short button; Str255 dest; Boolean trash; Boolean nuke; FSSpec spec; if (!tocH) return fnfErr; SetPrefLong(PREF_LAST_JUNK_TRIM,GMTDateTime()/3600); for (i=0;i<(*tocH)->count;i++) { if ((*tocH)->sums[i].arrivalSeconds < threshTime && (*tocH)->sums[i].spamScore >= threshScore) { BoxSetSummarySelected(tocH,i,true); count++; } else BoxSetSummarySelected(tocH,i,false); } if (count) { // warning? if (JunkPrefBoxArchiveWarning()) { button = ComposeStdAlert(Note,JUNK_EMPTY_WARNING,FILE_ALIAS_JUNK,count,FILE_ALIAS_JUNK,JUNK_MAILBOX_EMPTY_DAYS); if (CommandPeriod || button==kAlertStdAlertCancelButton) return userCanceledErr; if (button==kAlertStdAlertOtherButton) SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefBoxArchiveWarning); } // progress dialog OpenProgress(); ProgressMessage(kpTitle,ComposeRString(dest,TRIMMING_JUNK,FILE_ALIAS_JUNK)); // where are we going? GetRString(dest,JUNK_MAILBOX_EMPTY_DEST); if (EqualStrRes(dest,FILE_ALIAS_JUNK) || EqualStrRes(dest,JUNK)) { if (kAlertStdAlertOKButton==ComposeStdAlert(Stop,JUNK_JUNK_IS_BAD_TRIM_DEST,FILE_ALIAS_JUNK,FILE_ALIAS_TRASH)) { SetPref(JUNK_MAILBOX_EMPTY_DEST,""); GetRString(dest,TRASH); } else return dupFNErr; } trash = EqualStrRes(dest,TRASH) || EqualStrRes(dest,FILE_ALIAS_TRASH); nuke = AmQuitting && trash && PrefIsSet(PREF_AUTO_EMPTY) || StringSame("\p-",dest); // if we're quitting and the trash will be emptied, we can just nuke now if (nuke) DoIterativeThingyLo(tocH,MESSAGE_DELETE_ITEM,optionKey|shiftKey,0,false); else if (trash) DoIterativeThingyLo(tocH,MESSAGE_DELETE_ITEM,0,0,false); else if (!BoxSpecByName(&spec,dest)) MoveSelectedMessages(tocH,&spec,false); else ComposeStdAlert(Caution,JUNK_CANT_ARCHIVE,FILE_ALIAS_JUNK,FILE_ALIAS_JUNK,dest); // we may be on the hairy edge of life. Write the new toc if so if (AmQuitting) WriteTOC(tocH); } // Handle IMAP Junk mailboxes as well ArchiveIMAPJunk(); return noErr; } /************************************************************************ * ArchiveIMAPJunk - destroy junk in all IMAP Junk Mailboxes ************************************************************************/ static OSErr ArchiveIMAPJunk(void) { long i; uLong threshTime; uLong threshScore; long count = 0; short button; Str255 dest; Boolean trash; Boolean nuke; Boolean archiveIMAP; FSSpec spec; TOCHandle tocH, toTocH; MailboxNodeHandle junkMBox; Boolean bWarned = false; Handle uids = nil; long sumNum; MailboxNodeHandle destMBox; PersHandle destPers; PushPers(CurPers); for (CurPers = PersList; CurPers; CurPers = (*CurPers)->next) { if (IsIMAPPers(CurPers)) { threshTime = GMTDateTime() - GetRLong(JUNK_MAILBOX_EMPTY_DAYS) * 24 * 3600; threshScore = GetRLong(JUNK_MAILBOX_EMPTY_THRESH); SetPrefLong(PREF_LAST_JUNK_TRIM,GMTDateTime()/3600); // Find this personality's junk mailbox if (junkMBox = GetIMAPJunkMailbox(CurPers, false, true)) { spec = (*junkMBox)->mailboxSpec; if (tocH = TOCBySpec(&spec)) { // count the number of messages to move. Ignore deleted messages for (i=0;i<(*tocH)->count;i++) if (((*tocH)->sums[i].arrivalSeconds < threshTime) && ((*tocH)->sums[i].spamScore >= threshScore) && (((*tocH)->sums[i].opts&OPT_DELETED)==0)) count++; if (count) { // warning? if (!bWarned && JunkPrefBoxArchiveWarning()) { button = ComposeStdAlert(Note,JUNK_EMPTY_WARNING,FILE_ALIAS_JUNK,count,FILE_ALIAS_JUNK,JUNK_MAILBOX_EMPTY_DAYS); if (CommandPeriod || button==kAlertStdAlertCancelButton) return userCanceledErr; if (button==kAlertStdAlertOtherButton) SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefBoxArchiveWarning); bWarned = true; } // warn the user, we may have to go online for this if (Offline && GoOnline()) return(OFFLINE); // progress dialog OpenProgress(); ProgressMessage(kpTitle,ComposeRString(dest,TRIMMING_JUNK,FILE_ALIAS_JUNK)); // where are we going? toTocH = NULL; GetRString(dest,JUNK_MAILBOX_EMPTY_DEST); trash = EqualStrRes(dest,TRASH) || EqualStrRes(dest,FILE_ALIAS_TRASH); nuke = AmQuitting && trash && PrefIsSet(PREF_AUTO_EMPTY) || StringSame("\p-",dest); archiveIMAP = false; if (!nuke && !trash) { toTocH = NULL; if (!BoxSpecByName(&spec,dest)) { FSSpec imapSpec; imapSpec = spec; imapSpec.parID = SpecDirId(&spec); // Look in the local IMAP tree first for the destination mailbox destMBox = LocateNodeBySpec((*CurPers)->mailboxTree, &imapSpec); // If not found, look in all IMAP trees for the destination mailbox if (destMBox == NULL) LocateNodeBySpecInAllPersTrees(&imapSpec, &destMBox, &destPers); if (destMBox) { toTocH = TOCBySpec(&(*destMBox)->mailboxSpec); archiveIMAP = true; } else { // If still not found, use the local mailbox ... toTocH = TOCBySpec(&spec); } } } // Actually archive messages now ... if (nuke || trash || (archiveIMAP && toTocH)) { // build a list of UIDs to transfer ... uids = NuHandleClear(count*sizeof(unsigned long)); if (uids) { for (sumNum=0;sumNum<(*tocH)->count && count;sumNum++) if (((*tocH)->sums[sumNum].arrivalSeconds < threshTime) && ((*tocH)->sums[sumNum].spamScore >= threshScore) && (((*tocH)->sums[sumNum].opts&OPT_DELETED)==0)) ((unsigned long *)(*uids))[--count] = ((*tocH)->sums[sumNum].uidHash); // transfer them in the foreground. if (archiveIMAP) IMAPTransferMessages(tocH, toTocH, uids, false, true); else IMAPDeleteMessages(tocH, uids, nuke, true, false, true); } } else if (toTocH) { // we're archiving to a local mailbox IMAPStartFiltering(tocH, true); // display progress like we're filtering for (sumNum=0;sumNum<(*tocH)->count && count;sumNum++) if (((*tocH)->sums[sumNum].arrivalSeconds < threshTime) && ((*tocH)->sums[sumNum].spamScore >= threshScore) && (((*tocH)->sums[sumNum].opts&OPT_DELETED)==0)) IMAPMoveMessageDuringFiltering(tocH, sumNum, toTocH, false, NULL); IMAPStopFiltering(true); } else ComposeStdAlert(Caution,JUNK_CANT_ARCHIVE,FILE_ALIAS_JUNK,FILE_ALIAS_JUNK,dest); } // We're done with this Junk mailbox tocH = NULL; } } // else // junk mailbox could not be found. } } PopPers(); return noErr; } /************************************************************************ * SpecIsJunkSpec is a spec the junk mailbox? ************************************************************************/ Boolean SpecIsJunkSpec(FSSpecPtr spec) { return IsRoot(spec) && EqualStrRes(spec->name,JUNK); } /************************************************************************ * BoxIsJunkBox - is a tocH the junk mailbox? ************************************************************************/ Boolean BoxIsJunkBox(TOCHandle tocH) { return tocH && ( (*tocH)->which==JUNK || (*tocH)->imapTOC && IsIMAPJunkMailbox(TOCToMbox(tocH)) ); } /************************************************************************ * PreexistingJunkWarning - warn the user about their existing junk mailbox * I know it doesn't check for errors. There's not much we could do * except fail to launch, and that seems more drastic than just letting * things proceed. ************************************************************************/ void PreexistingJunkWarning(FSSpecPtr spec) { Boolean folder = FSpIsItAFolder(spec); short template = folder ? JUNK_PREEXISTING_FOLDER_WARNING:JUNK_PREEXISTING_WARNING; short button = ComposeStdAlert(Note,template,JUNK,JUNK); FSSpec newSpec; if (folder) { newSpec = *spec; GetRString(newSpec.name,JUNK_PREEXISTING_RENAME_NAME); UniqueSpec(&newSpec,100); FSpRename(spec,newSpec.name); FSpCreateResFile(spec,CREATOR,MAILBOX_TYPE,smSystemScript); } else if (button==kAlertStdAlertCancelButton) { FSSpec tocSpec; FSSpec newTOCSpec; // rename the mailbox newSpec = *spec; GetRString(newSpec.name,JUNK_PREEXISTING_RENAME_NAME); UniqueSpec(&newSpec,100); FSpCreateResFile(&newSpec,CREATOR,MAILBOX_TYPE,smSystemScript); FSpExchangeFiles(&newSpec,spec); // rename the toc file, if any Box2TOCSpec(spec,&tocSpec); if (FSpExists(&tocSpec)) { Box2TOCSpec(&newSpec,&newTOCSpec); FSpRename(&tocSpec,newTOCSpec.name); } } else if (CommandPeriod || button==kAlertStdAlertOtherButton) { // disable the feature SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefBoxHold); } // if the user says ok, we need do nothing special // Do not pass this way again SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefBoxExistWarning); } /************************************************************************ * JunkTOCCleanse - do some stuff to the junk box on open ************************************************************************/ void JunkTOCCleanse(TOCHandle tocH) { short i = (*tocH)->count; short spamXferScore = GetRLong(JUNK_XFER_SCORE); while (i--) { if ((*tocH)->sums[i].spamBecause==0) JunkSetScore(tocH,i,JUNK_BECAUSE_XFER,spamXferScore); } } /************************************************************************ * JunkRescan - rescan a mailbox for junk (not the junk mailbox) ************************************************************************/ OSErr JunkRescanBox ( TOCHandle tocH ) { OSErr err = noErr; short sumNum; uLong spamThresh = GetRLong ( JUNK_MAILBOX_THRESHHOLD ); TLMHandle tList = NULL; UseFeature(featureJunk); (void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL ); if ( tList != NULL ) { // we have at least one plugin! ASSERT ( tocH != NULL ); ASSERT ( tocH != GetJunkTOC ()); // rescore any we need to for ( sumNum = (*tocH)->count; sumNum-- ;) { CycleBalls(); // Save off the selected flag (*tocH)->sums[sumNum].spareShort = (*tocH)->sums[sumNum].selected; // If it's never been scored, or scored by a plugin, rescore it if ((*tocH)->sums[sumNum].spamScore == -1 || (*tocH)->sums[sumNum].spamBecause==JUNK_BECAUSE_PLUG) ScoreOneMessage ( tList, tocH, sumNum, kAutoScore ); } // transfer the rescored ones err = MoveToJunk ( tocH, spamThresh, NULL ); // restore the selection for ( sumNum = (*tocH)->count; sumNum-- ;) BoxSetSummarySelected ( tocH, sumNum, (*tocH)->sums[sumNum].spareShort ); DisposeHandle ((Handle) tList ); } return err; } /************************************************************************ * JunkRescan - rescan a the junk mailbox for junk ************************************************************************/ OSErr JunkRescanJunkMailbox () { FilterPB fpb; short sumNum; uLong spamThresh = GetRLong ( JUNK_MAILBOX_THRESHHOLD ); TOCHandle tocH = GetJunkTOC (); TLMHandle tList = NULL; // Bail if there isn't a junk mailbox if ( tocH == NULL ) return paramErr; UseFeature(featureJunk); (void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL ); if ( tList != NULL ) { // we have at least one plugin! // rescore any we need to for ( sumNum = (*tocH)->count; sumNum-- ;) { CycleBalls(); // Save off the selected flag (*tocH)->sums[sumNum].spareShort = (*tocH)->sums[sumNum].selected; // If it's never been scored, or scored by a plugin, rescore it if ((*tocH)->sums[sumNum].spamScore == -1 || (*tocH)->sums[sumNum].spamBecause==JUNK_BECAUSE_PLUG) ScoreOneMessage ( tList, tocH, sumNum, kAutoScore ); BoxSetSummarySelected ( tocH, sumNum, (*tocH)->sums[sumNum].spamScore < spamThresh ); } FilterSelectedMessages ( flkIncoming, tocH, &fpb); FilterPostprocess ( flkIncoming, &fpb ); // restore the selection for (sumNum=(*tocH)->count;sumNum--;) BoxSetSummarySelected ( tocH, sumNum, (*tocH)->sums[sumNum].spareShort ); DisposeHandle ((Handle) tList ); } return noErr; } /********************************************************************** * JunkSetScore - set a message's junk score, * handle virtual TOCs, too **********************************************************************/ OSErr JunkSetScore(TOCHandle tocH,short sumNum,short because,short score) { TOCHandle realTOC; short realSum; JunkSetScoreLo(tocH,sumNum,because,score); realTOC = GetRealTOC(tocH,sumNum,&realSum); if (realTOC && realTOC != tocH) { // do real mailbox also if working in virtual mailbox JunkSetScoreLo(realTOC,realSum,because,score); tocH = realTOC; sumNum = realSum; } SearchUpdateSum(tocH, sumNum, tocH, (*tocH)->sums[sumNum].serialNum, false, false); // Notify search window return noErr; } /************************************************************************ * JunkSetScoreLo - change the score of some junk ************************************************************************/ OSErr JunkSetScoreLo(TOCHandle tocH,short sumNum,short because,short score) { short junkThresh = GetRLong(JUNK_MAILBOX_THRESHHOLD); Boolean nowJunk = score >= junkThresh; Boolean wasJunk = (*tocH)->sums[sumNum].spamScore >= junkThresh; short oldBecause = (*tocH)->sums[sumNum].spamBecause; ASSERT(sumNum>=0 && sumNum<(*tocH)->count); if (sumNum<0 || sumNum >= (*tocH)->count) return fnfErr; (*tocH)->sums[sumNum].spamScore = score; (*tocH)->sums[sumNum].spamBecause = because; InvalTocBox(tocH,sumNum,blJunk); // Update stats here if (because==JUNK_BECAUSE_WHITE) UpdateNumStatWithTime(kStatWhiteList,1,(*tocH)->sums[sumNum].seconds); else if (because==JUNK_BECAUSE_PLUG) { if (nowJunk) UpdateNumStatWithTime(kStatScoredJunk,1,(*tocH)->sums[sumNum].seconds); else UpdateNumStatWithTime(kStatScoredNotJunk,1,(*tocH)->sums[sumNum].seconds); } else if (because==JUNK_BECAUSE_USER) { if (nowJunk && !wasJunk) { // something missed it. What? if (oldBecause == JUNK_BECAUSE_WHITE) UpdateNumStatWithTime(kStatFalseWhiteList,1,(*tocH)->sums[sumNum].seconds); else if (oldBecause == JUNK_BECAUSE_PLUG) UpdateNumStatWithTime(kStatFalseNegatives,1,(*tocH)->sums[sumNum].seconds); } else if (!nowJunk && wasJunk) { // Did we misjunk it? if (oldBecause == JUNK_BECAUSE_PLUG) { UpdateNumStatWithTime(kStatFalsePositives,1,(*tocH)->sums[sumNum].seconds); } } } return noErr; } /************************************************************************ * JunkIntro - introduce the junk feature ************************************************************************/ #define jiNoItem 2 short JunkIntro(void) { extern ModalFilterUPP DlgFilterUPP; short item=0; if (ETLCountTranslators(EMSF_JUNK_MAIL)) { SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefNeedIntro); Nag (JUNK_INTRO_DLOG, nil, NagHitReturnItem, DlgFilterUPP, false, (uLong)&item); if (CommandPeriod || item==jiNoItem) SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefBoxHold); ASSERT(item!=0); } return item; } /************************************************************************ * NagHitReturnItem - hit proc that stuffs the item hit into the refcon, used as pointer to short ************************************************************************/ Boolean NagHitReturnItem(EventRecord *event, DialogPtr theDialog, short itemHit, long dialogRefcon) { *(short*)dialogRefcon = itemHit; return (true); } /************************************************************************ * JunkTrimOK - is it time to trim junk? ************************************************************************/ Boolean JunkTrimOK(void) { long daysSince = (GMTDateTime()/3600-GetPrefLong(PREF_LAST_JUNK_TRIM)+8)/24; short interval = GetRLong(JUNK_TRIM_INTERVAL); // If we're quitting, follow the interval strictly // we also follow the interval strictly if the user has a hyper interval set if (AmQuitting || GetRLong(JUNK_MAILBOX_EMPTY_DAYS)==1) return interval && daysSince >= interval; // If the user might be in the middle of something, don't be quite so cheeky else return interval && daysSince >= 3*interval; } /************************************************************************ * JunkReassignKeys - reassign command keys from filter to junk ************************************************************************/ void JunkReassignKeys(Boolean switchem) { MenuHandle messageMH = GetMHandle(MESSAGE_MENU); MenuHandle specialMH = GetMHandle(SPECIAL_MENU); short key; if (switchem) { GetItemCmd(specialMH,AdjustSpecialMenuItem(SPECIAL_FILTER_ITEM),&key); if (key) { SetItemCmd(specialMH,AdjustSpecialMenuItem(SPECIAL_FILTER_ITEM),0); SetItemCmd(messageMH,MESSAGE_JUNK_ITEM,key); SetItemCmd(messageMH,MESSAGE_NOTJUNK_ITEM,key); SetMenuItemModifiers(messageMH,MESSAGE_NOTJUNK_ITEM,kMenuOptionModifier); } } else { GetItemCmd(messageMH,MESSAGE_JUNK_ITEM,&key); if (key) { SetItemCmd(messageMH,MESSAGE_JUNK_ITEM,0); SetItemCmd(specialMH,AdjustSpecialMenuItem(SPECIAL_FILTER_ITEM),key); SetItemCmd(messageMH,MESSAGE_NOTJUNK_ITEM,0); } } if (switchem) ClearPrefBit(PREF_JUNK_MAILBOX,bJunkPrefSwitchCmdJ); else SetPrefBit(PREF_JUNK_MAILBOX,bJunkPrefSwitchCmdJ); } /************************************************************************ * JunkItemEnable - should the junk menu item be enabled? ************************************************************************/ Boolean JunkItemsEnable(MyWindowPtr win,Boolean not) { WindowPtr winWP = GetMyWindowWindowPtr(win); TOCHandle tocH; short sumNum; FSSpec spec; if (!win || !winWP) return false; Zero(spec); switch (GetWindowKind(winWP)) { case CBOX_WIN: return false; case COMP_WIN: return false; case MESS_WIN: if (JunkPrefAlwaysEnable()) return true; tocH = (*Win2MessH(win))->tocH; return not==BoxIsJunkBox(tocH); case MBOX_WIN: tocH = Win2TOC(win); if (!(win->hasSelection || MyWinHasSelection(win))) return false; if (JunkPrefAlwaysEnable()) return true; if (!(*tocH)->virtualTOC) return not==BoxIsJunkBox(tocH); // the non-trivial case here for (sumNum=(*tocH)->count-1;sumNum>=0;sumNum--) { if ((*tocH)->sums[sumNum].selected) { if ((*tocH)->sums[sumNum].flags&FLAG_OUT) return false; spec = GetMailboxSpec(tocH,sumNum); if (not!=SpecIsJunkSpec(&spec)) return false; } } // if we found any selected messages, we'll have // put their spec in here, and so we should enable return *spec.name!=0; } // failure return false; } #pragma mark ** Call Junk Plugins ** /************************************************************************ * ScoreOneMessage - * Score a message using each of the installed plugins * Store the high score (and the signature of the plugin) * into the TOC for the mailbox. ************************************************************************/ void ScoreOneMessage ( TLMHandle tList, TOCHandle tocH, short sumNum, TJunkType howToScore ) { OSErr err = noErr; short highScore = -2; short highSig = 0; Handle text; ASSERT ( tList != NULL ); ASSERT ( HandleCount ( tList ) > 0 ); // don't score plugins if we've been asked not to if (!CanScoreToc(tocH)) return; // Load the message ... if ((*tocH)->imapTOC) CacheIMAPMessageForSpamWatch(tocH, sumNum); else CacheMessage ( tocH, sumNum ); if ( text = (*tocH)->sums[sumNum].cache ) { Handle headers, body; char *ret; long bodyOffset, textSz, hdrSz, envSz; ASSERT ( *text != NULL ); // Split the message into headers and body // Error detection here would be a good thing!! // The first line is always a UUCP envelope, followed by a // It's not part of the message, so we strip it out. ret = strchr ( *text, '\015' ); ASSERT ( ret != NULL ); envSz = ret + 1 - *text; // how much of the buffer to throw away // The headers are seperated from the body by a sequence bodyOffset = BodyOffset ( text ); // first byte of the body textSz = GetHandleSize ( text ); // total size of the message hdrSz = bodyOffset - envSz; // bytes in the header if ( bodyOffset == textSz ) { // couldn't find "\n\n" - empty message body? body = NULL; } else { long bodySz = textSz - bodyOffset; ASSERT ( bodySz > 0 ); body = NewHandleClear ( bodySz + 1 ); BlockMoveData ( *text + bodyOffset, *body, bodySz ); } ASSERT ( hdrSz > 0 ); headers = NewHandleClear ( hdrSz + 1 ); BlockMoveData ( *text + envSz, *headers, hdrSz ); // For each translator in the list, score the message if ( err == noErr ) { emsJunkScore junkScore; emsResultStatus junkStatus; emsMessageInfo messageInfo; emsHeaderData messageHdr; emsMIMEtype mimeInfo; emsTranslator transInfo; emsJunkInfo junkInfo; TLMPtr aModule; short i, numPlugins; Zero ( messageHdr ); messageHdr.size = sizeof ( messageHdr ); Zero ( messageInfo ); messageInfo.size = sizeof ( messageInfo ); Zero ( mimeInfo ); mimeInfo.size = sizeof ( mimeInfo ); Zero ( junkInfo ); junkInfo.size = sizeof ( junkInfo ); messageInfo.fromAddressStatus = emsAddressNotChecked; // !!! for now messageInfo.header = &messageHdr; messageInfo.textType = &mimeInfo; messageInfo.text = body; junkInfo.context = EMSFJUNK_SCORE_ON_ARRIVAL; messageHdr.rawHeaders = headers; // Need to fill in more of mimeInfo and messageInfo // And the fromAddressStatus, too! HLock ((Handle) tList ); numPlugins = HandleCount ( tList ) - 1; // don't call the sentinel! for ( i = 0; i < numPlugins; ++i ) { aModule = &(*tList) [ i ]; Zero ( junkScore ); junkScore.size = sizeof ( junkScore ); Zero ( junkStatus ); junkStatus.size = sizeof ( junkStatus ); Zero ( transInfo ); transInfo.size = sizeof ( transInfo ); transInfo.id = aModule->id; if ( howToScore != kAutoScore ) { // Fill in the old score info here junkScore.score = (*tocH)->sums[sumNum].spamScore; junkInfo.context = howToScore == kUserMarkJunk ? EMSFJUNK_MARK_IS_JUNK : EMSFJUNK_MARK_NOT_JUNK; junkInfo.pluginID = (*tocH)->sums[sumNum].spamBecause; (void) ETLMarkJunk ( aModule, &transInfo, &junkInfo, &messageInfo, &junkScore, &junkStatus ); } else { if ( noErr == (err = ETLScoreJunk ( aModule, &transInfo, &junkInfo, &messageInfo, &junkScore, &junkStatus ))) { if ( junkScore.score > highScore && highScore != -1 ) { highScore = junkScore.score; highSig = transInfo.id; // !!! Is this right ?? } else if ( junkScore.score == -1 ) { highScore = -1; } } } } HUnlock ((Handle) tList ); } if ( body != NULL ) DisposeHandle ( body ); if ( headers != NULL ) DisposeHandle ( headers ); } // We want to record scores of zero or -1 that come out // of plug-ins. The only time we don't put a score // on a message is when no plug-in ran successfully on it if ( highScore > -2 && howToScore == kAutoScore ) // we're not recording id's right now // JunkSetScore ( tocH, sumNum, highSig, highScore ); JunkSetScore ( tocH, sumNum, JUNK_BECAUSE_PLUG, MAX(0,highScore) ); // cleanup after CacheIMAPMessage. // Zap the cache, unless this message still needs to be filtered. if ((*tocH)->imapTOC) if (((*tocH)->sums[sumNum].flags&FLAG_UNFILTERED) == 0) ZapHandle((*tocH)->sums[sumNum].cache); } /************************************************************************ * JunkScoreBox - Score all the messages in a mailbox ************************************************************************/ void JunkScoreBox ( TOCHandle tocH, short first, short last, Boolean rescore ) { TLMHandle tList = NULL; short i; Boolean whiteCheck = JunkPrefWhiteList (); Boolean midCheck = JunkPrefWhiteListReplies(); ASSERT ( tocH != NULL ); UseFeature(featureJunk); (void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL ); if ( first < 0 ) first = 0; if ( last < 0 || last >= (*tocH)->count ) last = (*tocH)->count - 1; // We want to run through all these messages even if we have no junk plugins // because we might want to mark some for whitelisting. for ( i = first; i <= last; ++i ) if (rescore || !(*tocH)->sums[i].spamBecause) { if (midCheck) { if (WhiteListByMID(tocH,i)) { JunkSetScore(tocH,i,JUNK_BECAUSE_WHITE,0); continue; } } if ( whiteCheck ) { EnsureFromHash ( tocH, i ); if ((*tocH)->sums [ i ].fromHash != kNoMessageId ) if ( HashAppearsInAliasFile ((*tocH)->sums[i].fromHash, nil )) { JunkSetScore ( tocH, i , JUNK_BECAUSE_WHITE, 0 ); continue; } } // Otherwise CycleBalls (); if ( tList != NULL ) ScoreOneMessage ( tList, tocH, i, kAutoScore ); } if ( tList != NULL ) DisposeHandle ((Handle) tList ); } /************************************************************************ * JunkScoreIMAPBox - Score the messages in an IMAP mailbox. * * Like JunkScoreBox, but handles IMAP nuances. Specify first and last * for a range to junk, (-1) for each to score the entire mailbox, and * bUnfilteredOnly to do unfiltered only. * * Very rarely would one want to score an entire IMAP mailbox, certainly * not after a mailcheck as we do in POP. This routine is cirtually * identical to its POP equivalent, but allows for scoring of messages * about to be filtered as well. ************************************************************************/ void JunkScoreIMAPBox ( TOCHandle tocH, short first, short last, Boolean bUnfilteredOnly ) { TLMHandle tList = NULL; short i; Boolean whiteCheck = JunkPrefWhiteList (); UseFeature(featureJunk); (void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL ); if ( tList != NULL ) { if ( first < 0 ) first = 0; if ( last < 0 || last >= (*tocH)->count ) last = (*tocH)->count - 1; for ( i = first; i <= last; ++i ) { // only do unfiltered messages if the caller has requested that if (bUnfilteredOnly && (((*tocH)->sums[i].flags&FLAG_UNFILTERED)==0)) continue; if ( whiteCheck ) { EnsureFromHash ( tocH, i ); if ((*tocH)->sums [ i ].fromHash != kNoMessageId ) if ( HashAppearsInAliasFile ((*tocH)->sums[i].fromHash, nil )) { JunkSetScore ( tocH, i , JUNK_BECAUSE_WHITE, 0 ); continue; } } // Otherwise CycleBalls (); ScoreOneMessage ( tList, tocH, i, false ); } DisposeHandle ((Handle) tList ); } } /************************************************************************ * JunkScoreSelected - Score the selected messages in a mailbox ************************************************************************/ void JunkScoreSelected ( TOCHandle tocH ) { TLMHandle tList = NULL; short i; Boolean whiteCheck = JunkPrefWhiteList (); ASSERT ( tocH != NULL ); UseFeature(featureJunk); (void) ETLListAllTranslators ( &tList, EMSF_JUNK_MAIL ); // We want to run through all these messages even if we have no junk plugins // because we might want to mark some for whitelisting. for ( i = 0; i < (*tocH)->count; ++i ) { if ((*tocH)->sums [ i ].selected ) { if ( whiteCheck ) { EnsureFromHash ( tocH, i ); if ((*tocH)->sums [ i ].fromHash != kNoMessageId ) if ( HashAppearsInAliasFile ((*tocH)->sums[i].fromHash, nil )) { JunkSetScore ( tocH, i , JUNK_BECAUSE_WHITE, 0 ); continue; } } // Otherwise CycleBalls (); if ( tList != NULL ) ScoreOneMessage ( tList, tocH, i, kAutoScore ); } } if ( tList != NULL ) DisposeHandle ((Handle) tList ); } // Can we actually score mail? // Really a shorthand for "are there any plugins that can score mail installed"? Boolean CanScoreJunk () { TLMHandle tList = NULL; OSErr err = ETLListAllTranslators( &tList, EMSF_JUNK_MAIL ); if ( tList != NULL ) DisposeHandle ((Handle) tList ); return err == noErr; } /************************************************************************ * CanScoreToc - Can we score this tocH? * False if it's an IMAP mailbox and bJunkPrefIMAPNoRunPlugins is set * True in all other cases ************************************************************************/ Boolean CanScoreToc( TOCHandle tocH ) { Boolean bScore = true; PersHandle pers; if (tocH == NULL) return (false); pers = TOCToPers(tocH); if ((pers != NULL) && IsIMAPPers(pers)) { PushPers(pers); bScore = !JunkPrefIMAPNoRunPlugins(); PopPers(); } return (bScore); } /************************************************************************ * JunkMoveIMAPMessages - go through a virtual toc and move all selected * IMAP messages to the junk or inbox folder * * This is fairly tricky as * (a) the virtual toc contains messages in different IMAP mailboxes * (b) the messages being moved may go to different destinations * (c) IMAP transfers suck in that they can take a long time * (d) there may be POP messages in the mailbox we need to ignore * * This routine sets up one transfer operation per imap mailbox * represented in the virtual toc. ************************************************************************/ void JunkMoveIMAPMessages(TOCHandle tocH, Boolean isJunk) { short i, j; TOCHandle realToc, destTocH; short realSum; MailboxNodeHandle dest; PersHandle pers; Accumulator a; Handle uids; for (i=0; i < (*tocH)->count; i++) { if (((*tocH)->sums[i].selected) && (((*tocH)->sums[i].opts&OPT_DELETED)==0)) { // initialize destTocH = NULL; // is this selected message an IMAP message? realToc = GetRealTOC(tocH,i,&realSum); if (realToc && (*realToc)->imapTOC) { pers = TOCToPers(realToc); if (pers) { // where is this particular IMAP message going? if (!isJunk) { // we're not junking something in the junk mailbox if (BoxIsJunkBox(realToc)) { // Put the message back into the proper inbox. if ((dest = LocateInboxForPers(pers)) != NULL) destTocH = TOCBySpec(&(*dest)->mailboxSpec); } } else { // we're junking something in something other than the junk box if (!BoxIsJunkBox(realToc)) { // Get the proper JUNK mailbox dest = GetIMAPJunkMailbox(pers, true, false); if (dest && (dest != TOCToMbox(realToc))) destTocH = TOCBySpec(&(*dest)->mailboxSpec); } } } } // build a list of messages to transfer AccuInit(&a); // properly handle all of the messages in this real toc for (j = i; j < (*tocH)->count; j++) { if (((*tocH)->sums[j].selected) && (((*tocH)->sums[j].opts&OPT_DELETED)==0)) { TOCHandle rt; short rs; rt = GetRealTOC(tocH, j, &rs); // is this message in the same IMAP mailbox we're currently considering? if (rt && (rt == realToc) && ((*rt)->imapTOC)) { // are we moving this message? if (destTocH) { // add it to the list of messages to transfer AccuAddPtr(&a, &((*rt)->sums[rs].uidHash), sizeof(long)); // invalidate the summary in the search window (*tocH)->sums[j].u.virtualMess.virtualMBIdx = -1; InvalSum(tocH, j); } // do not reprocess this message (*tocH)->sums[j].selected = false; } } } // move the messages if they're going anywhere. if (destTocH) { AccuTrim(&a); uids = a.data; a.data = NULL; IMAPTransferMessages(realToc, destTocH, uids, false, false); } AccuZap(a); } } } /************************************************************************ * WhiteListByMID - are any of a message's references on a list of messages we sent? ************************************************************************/ Boolean WhiteListByMID(TOCHandle tocH, short sumNum) { Handle text; if (SumFlagIsSet(tocH,sumNum,FLAG_KNOWS_ME)) return true; if (CacheMessage(tocH,sumNum)) return false; if (!OutgoingMIDList.offset) return false; if (text=(*tocH)->sums[sumNum].cache) { Handle references = nil; HNoPurge(text); if (!HandleHeadGetIdText(text,HeaderStrn+REFERENCES_HEAD,&references)) { BinAddrHandle addresses=nil; Tr(references," \t\r\n",",,,,"); if (!SuckAddresses(&addresses,references,false,false,false,nil)) { UPtr address; for (address = *addresses; *address; address += *address+2) { if (AccuFindLong(&OutgoingMIDList,Hash(address))>=0) { SetSumFlag(tocH,sumNum,FLAG_KNOWS_ME); break; } } ZapHandle(addresses); } ZapHandle(references); } HPurge(text); } // did we win? return SumFlagIsSet(tocH,sumNum,FLAG_KNOWS_ME); }