/* 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 "filtrun.h" /************************************************************************ * Filters window - Copyright (C) 1994 QUALCOMM Incorporated ************************************************************************/ #define FILE_NUM 69 #ifdef TWO #pragma segment FILTRUN #define MAX_FILTER_PASS 10 Boolean DoesIntersectNick(UHandle nickAddresses,UHandle nickExpanded,UPtr spot,long len); Boolean DoesIntersectNickFile(PStr file,UPtr spot,long len); Boolean TermDateMatch(MTPtr mt, TOCHandle tocH, short sumNum); Boolean TermJunkMatch(MTPtr mt, TOCHandle tocH, short sumNum); Boolean TermPersMatch(MTPtr mt, TOCHandle tocH, short sumNum); Boolean TermPriorMatch(MTPtr mt, TOCHandle tocH, short sumNum); Boolean AnyFilters(FilterKeywordEnum fType); Boolean AnyFiltersLo(FilterKeywordEnum fType,Handle hFilters); Boolean RightFilterType(FilterKeywordEnum fType,short filter); Boolean FilterMatch(short filter,TOCHandle tocH, short sumNum,FilterPBPtr fpb); OSErr TakeFilterAction(short filter,FilterPBPtr fpb,Boolean noXfer); Boolean TermMatch(MTPtr mt,TOCHandle tocH, short sumNum,FilterPBPtr fpb); Boolean TermPtrMatch(MTPtr mtmt,UPtr spot,UPtr end); void Filter1Postprocess(FilterKeywordEnum fType,FilterPBPtr fpb); OSErr FilterMessageLo(FilterKeywordEnum fType,TOCHandle tocH,short sumNum,FilterPBPtr fpb,Boolean noXfer); Boolean TermExpMatch(MTPtr mtmt, UPtr spot, UPtr end,UHandle *cache); void FiltLogMatch(short filter,TOCHandle tocH,short sumNum); uLong FilterLastMatch(short filter); Boolean FromIntersectNickFile(MTPtr mt, TOCHandle tocH,short sumNum); Boolean FromIntersectNickFileMatch(MTPtr mt, TOCHandle tocH,short sumNum); OSErr FGlobalErr; /********************************************************************** * Filter1PostProcess - postprocessing of a single filter **********************************************************************/ void Filter1Postprocess(FilterKeywordEnum fType,FilterPBPtr fpb) { FSSpec spec; Boolean openPref = !PrefIsSet(PREF_NO_OPEN_IN); Boolean openBox = (openPref && !fpb->dontUser && fType==flkIncoming) || fpb->openMailbox; short which; if (fpb->xferred) { spec = fpb->spec; which = 0; if (!fpb->xferredFromIMAP) // if the message was filtered from an IMAP account, report where it went no matter what. { if (spec.parID==MailRoot.dirId && spec.vRefNum==MailRoot.vRef) if (EqualStrRes(spec.name,IN)) which = IN; else if (EqualStrRes(spec.name,OUT)) which = OUT; else if (EqualStrRes(spec.name,TRASH)) which = TRASH; } } else { spec = GetMailboxSpec(fpb->tocH,-1); which = (*fpb->tocH)->which; #ifdef THREADING_ON if (which==IN_TEMP) { TOCHandle tocH=GetRealInTOC(); which=IN; ASSERT(tocH); if (!tocH) return; spec=GetMailboxSpec(tocH,-1); } #ifdef BATCH_DELIVERY_ON else if (IsDelivery(&spec)) { spec.vRefNum = MailRoot.vRef; spec.parID = MailRoot.dirId; GetRString(spec.name,IN); } #endif #endif } //fpb->doNotifyThing = fpb->notify; // IMAP - make sure we open/report the visible TOC, not the hidden one. if ((*(fpb->tocH))->imapTOC) GetRealIMAPSpec(spec, &spec); if (spec.name) { /* * do we need to open the mailbox? */ if (openBox && (fpb->xferred||!openPref)) AddSpecToList(&spec,fpb->mailbox); /* * do we need to open the message? */ if (fpb->openMessage) { // if this message has been transferred to an IMAP mailbox, we won't be able to open it. if (!IsIMAPMailboxFile(&spec) || !fpb->xferred) AddSpecToList(&spec,fpb->message); if (!fpb->xferred) (*fpb->tocH)->sums[fpb->sumNum].opts |= OPT_OPEN; } /* * do we need to do the report? */ if ((fpb->doReport || (PrefIsSet(PREF_REPORT)&&!which)) && !fpb->dontReport) AddSpecToList(&spec,fpb->report); } /* * how about normal notification? */ if (fpb->xferred || fpb->dontUser) { fpb->notify--; if (fpb->dontUser || (which==TRASH)) fpb->doNotifyThing--; if (!fpb->xferred) (*fpb->tocH)->sums[fpb->sumNum].opts &= ~OPT_NOTIFY; } else (*fpb->tocH)->sums[fpb->sumNum].opts |= OPT_NOTIFY; /* * printing? */ if (fpb->print && !fpb->xferred) { short oldstat = (*fpb->tocH)->sums[fpb->sumNum].state; PrintClosedMessage(fpb->tocH,fpb->sumNum,True); SetState(fpb->tocH,fpb->sumNum,oldstat); } } /********************************************************************** * FilterPostProcess - take all the final actions needed after filtering **********************************************************************/ void FilterPostprocess(FilterKeywordEnum fType,FilterPBPtr fpb) { #pragma unused(fType) CSpec spec; short n; short i; WindowPtr behindWP=nil; ComposeLogS(LOG_FILT,nil,"\pPostprocess %d %d",HandleCount(fpb->mailbox),HandleCount(fpb->message)); ZapHandle(fpb->ccAddresses); ZapHandle(fpb->bccAddresses); ZapHandle(fpb->toAddresses); /* * first, we make the filter report */ if (fpb->report && GetHandleSize_(fpb->report)) {GenSpecWindow(fpb->report);ZapHandle(fpb->report);} /* * Expunge any IMAP mailbox that may have been touched during filtering */ IMAPPostFilterExpunge(); /* * now, we open mailboxes */ if (fpb->mailbox) { TOCHandle tocH = GetSpecialTOC(IN); WindowPtr tocWinWP = nil; behindWP = OpenBehindMePlease(); if (tocH && (*tocH)->win) { // if in box open, show it first tocWinWP = GetMyWindowWindowPtr ((*tocH)->win); if (!PrefIsSet(PREF_NO_OPEN_IN) && fpb->notify && fType==flkIncoming && IsWindowVisible(tocWinWP)) { if (!behindWP) { SelectWindow_(tocWinWP); behindWP = GetMyWindowWindowPtr((*tocH)->win); } else SendBehind (tocWinWP,behindWP); } } n=HandleCount(fpb->mailbox); for (i=0;imailbox)[i]; if (tocH=TOCBySpec(&spec.spec)) { ShowBoxAt(tocH,(*tocH)->previewPTE ? -1:FumLub(tocH),behindWP); if (PrefIsSet(PREF_ZOOM_OPEN)) ReZoomMyWindow(GetMyWindowWindowPtr((*tocH)->win)); if (!behindWP && tocWinWP) UpdateMyWindow(tocWinWP); // make sure topmost one updates NOW behindWP = GetMyWindowWindowPtr((*tocH)->win); } } } /* * now, we open messages */ if (fpb->message) { n=HandleCount(fpb->message); while (n--) { spec = (*fpb->message)[n]; OpenFilterMessages(&spec.spec); } } /* * and sounds */ if (fpb->sounds) { n=HandleCount(fpb->sounds); while (n--) PlaySoundId((*fpb->sounds)[n]); } /* * Resynchronize any IMAP mailbox that may have been touched during filtering */ if (!gSkipIMAPBoxes) { IMAPStopFiltering(true); // done doing IMAP filtering at this point. IMAPPostFilterResync(); } /* * done */ ZapHandle(fpb->message); ZapHandle(fpb->mailbox); ZapHandle(fpb->report); ZapHandle(fpb->sounds); } /********************************************************************** * InitFPB - initialize an FPB **********************************************************************/ OSErr InitFPB(FilterPBPtr fpb,Boolean zapAddrs,Boolean listsToo) { FilterPB saveFPB = *fpb; // user requested we clear addresses if (zapAddrs) { ZapHandle(fpb->toAddresses); ZapHandle(fpb->ccAddresses); ZapHandle(fpb->bccAddresses); } // zero it all Zero(*fpb); // get the header names GetRString(fpb->to,HEADER_STRN+TO_HEAD); GetRString(fpb->cc,HEADER_STRN+CC_HEAD); GetRString(fpb->bcc,HEADER_STRN+BCC_HEAD); // allocate or restore lists if (listsToo) { if (!(fpb->message = NuHandle(0)) || !(fpb->mailbox = NuHandle(0)) || !(fpb->report = NuHandle(0))) return(MemError()); } else { fpb->message = saveFPB.message; fpb->mailbox = saveFPB.mailbox; fpb->report = saveFPB.report; fpb->sounds = saveFPB.sounds; fpb->doNotifyThing = saveFPB.doNotifyThing; fpb->notify = saveFPB.notify; } return(noErr); } /************************************************************************ * FilterSelectedMessage - filter the selection from a mailbox ************************************************************************/ OSErr FilterSelectedMessages(FilterKeywordEnum fType,TOCHandle tocH,FilterPBPtr fpb) { short err=noErr; short sumNum; short countWas; short lastSelected = -1; short initialCount = (*tocH)->count; Boolean isOut = (*tocH)->which==OUT; uLong pTicks=0; long count; TOCHandle realTocH; short realSumNum; long realSearialNum; FSSpec inSpec; TOCHandle inTocH; Boolean justFakingIt = false; Boolean bHidden = false; if (fType==flkIncoming) { if (!(inTocH=GetRealInTOC())) return(userCanceledErr); inSpec = GetMailboxSpec(inTocH,-1); } if (err=InitFPB(fpb,false,true)) return(WarnUser(MEM_ERR,err)); if (!PrefIsSet(PREF_MA)) { if (err=RegenerateFilters()) return(err); justFakingIt = fType==flkIncoming && !AnyFilters(fType); if (justFakingIt || AnyFilters(fType)) { if ((*tocH)->imapTOC) IMAPStartManualFiltering(); count = CountSelectedMessages(tocH); if (count>10) OpenProgress(); ProgressMessageR(kpTitle,FILTERING); ProgressMessageR(kpSubTitle,LEFT_TO_PROCESS); Progress(NoBar,count,nil,nil,nil); for (sumNum=0;sumNum<(*tocH)->count;sumNum++) { #ifdef SPEECH_ENABLED (void) SpeechIdle (); #endif if (count>1) MiniEvents(); if (CommandPeriod) break; if ((*tocH)->sums[sumNum].selected) { fpb->notify++; if (!(--count%10) || TickCount()-pTicks>30) { Progress(NoBar,count,nil,nil,nil); pTicks = TickCount(); } lastSelected = sumNum; countWas = (*tocH)->count; /* * load up cache */ if ((realTocH = GetRealTOC(tocH,sumNum,&realSumNum))) { realSearialNum = (*realTocH)->sums[realSumNum].serialNum; CacheMessage(realTocH,realSumNum); if (!(*realTocH)->imapTOC) { if (!(*realTocH)->sums[realSumNum].cache) { WarnUser(MEM_ERR,MemError()); err = 1; } else HNoPurge((*realTocH)->sums[realSumNum].cache); } /* * do the filtering */ if (!err && !justFakingIt) err = FilterMessageLo(fType,realTocH,realSumNum,fpb,False); if (!err && fType==flkIncoming) { // transfer to in if (!(err=MoveMessageLo(realTocH,realSumNum, &inSpec, false, false, true))) err = euFilterXfered; } // Hide the message if it was deleted. if ((*tocH)->imapTOC) bHidden = ShowHideFilteredSummary(tocH, sumNum); // adjust the count to account for messages that are removed from the toc during the filtering process if (bHidden || ((err==euFilterXfered) && ((!(*tocH)->imapTOC && !(*tocH)->virtualTOC)))) { // a message was either moved into the hidden IMAP cache, // or a message was transferred from a non IMAP, non virtual toc sumNum--; InvalContent((*tocH)->win); UpdateMyWindow(GetMyWindowWindowPtr((*tocH)->win)); } /* * clean up after */ if (err==euFilterXfered) { // update the search window if it's open if ((*realTocH)->imapTOC) (*tocH)->sums[sumNum].u.virtualMess.virtualMBIdx = -1; if (!bHidden) InvalSum(tocH, sumNum); } else { if ((*realTocH)->sums[realSumNum].cache) HPurge((*realTocH)->sums[realSumNum].cache); } } if (err==euFilterStop||err==euFilterXfered) err = noErr; if (err || CommandPeriod) break; } } // we're done filtering IMAPStopFiltering(true); CloseProgress(); } FiltersDecRef(); } NotifyHelpers(0,eManFilter,tocH); if (initialCount>(*tocH)->count && !CommandPeriod) BoxSelectAfter((*tocH)->win,lastSelected); return(err); } /************************************************************************ * FilterFlaggedMessages - filter the flagged messages in a mailbox ************************************************************************/ OSErr FilterFlaggedMessages(FilterKeywordEnum fType,TOCHandle tocH,FilterPBPtr fpb) { short err=noErr; short sumNum; short countWas; short lastSelected = -1; short initialCount = (*tocH)->count; Boolean isOut = (*tocH)->which==OUT; uLong pTicks=0; long count = CountFlaggedMessages(tocH); fpb->doNotifyThing = fpb->notify = count; if (err=RegenerateFilters()) return(err); if (AnyFilters(fType)) { if (count>5) OpenProgress(); ProgressMessageR(kpTitle,FILTERING); ProgressMessageR(kpSubTitle,LEFT_TO_PROCESS); Progress(NoBar,count,nil,nil,nil); fpb->doNotifyThing = fpb->notify = 0; for (sumNum=0;sumNum<(*tocH)->count;sumNum++) { #ifdef SPEECH_ENABLED (void) SpeechIdle (); #endif if (count>1) MiniEvents(); if (CommandPeriod) break; if ((*tocH)->sums[sumNum].flags&FLAG_UNFILTERED) { fpb->notify++; fpb->doNotifyThing++; if (!(--count%10) || TickCount()-pTicks>30) { Progress(NoBar,count,nil,nil,nil); pTicks = TickCount(); } lastSelected = sumNum; countWas = (*tocH)->count; /* * load up cache */ CacheMessage(tocH,sumNum); if (!(*tocH)->imapTOC) { if (!(*tocH)->sums[sumNum].cache) { WarnUser(MEM_ERR,MemError()); err = 1; } else HNoPurge((*tocH)->sums[sumNum].cache); } /* * do the filtering */ if (!err) err = FilterMessageLo(fType,tocH,sumNum,fpb,False); /* * clean up after */ if ((*tocH)->sums[sumNum].cache) HPurge((*tocH)->sums[sumNum].cache); if (err==euFilterStop||err==euFilterXfered) err = noErr; if (err || CommandPeriod) break; } } IMAPStopFiltering(true); CloseProgress(); } FiltersDecRef(); NotifyHelpers(0,eManFilter,tocH); if (initialCount>(*tocH)->count && !CommandPeriod) BoxSelectAfter((*tocH)->win,lastSelected); return(err); } /************************************************************************ * FilterFlaggedMessagesIncrementally - spam score and filter messages in * an IMAP mailbox a chunk at a time. ************************************************************************/ OSErr FilterIMAPTocIncrementally(TOCHandle tocH,FilterPBPtr fpb,Boolean noXfer) { short err = noErr; short sumNum; short filterHogTicks = GetRLong(FILTER_HOG_TICKS); uLong startTick = TickCount(); Boolean dirty = false; long spamThresh = GetRLong(JUNK_MAILBOX_THRESHHOLD); FSSpec mailboxspec; // init mailboxspec.name[0] = 0; // regenerate filters if (err=RegenerateFilters()) return(err); beginFilter: for (sumNum=0;sumNum<(*tocH)->count;sumNum++) { // skip messages that don't need to be filtered if (((*tocH)->sums[sumNum].flags&FLAG_UNFILTERED) == 0) continue; //filter the whole darn thing if need more time and we got the idle time //we need more time if we didn't filter any messages and we ran out of time if (EventPending()) return kNotEnoughTime; if (TickCount() - startTick > filterHogTicks) if (dirty) return kNotEnoughTime; #ifdef SPEECH_ENABLED (void) SpeechIdle (); #endif if (CommandPeriod) break; CheckSLIP(); fpb->doNotifyThing++; fpb->notify++; // // score the message. We'll junk it when filtering completes. // if (HasFeature(featureJunk) && CanScoreJunk()) { JunkScoreIMAPBox(tocH, sumNum, sumNum, false); // Junk it if we ought to if (JunkPrefBoxHold() && ((*tocH)->sums[sumNum].spamScore >= spamThresh)) { (*tocH)->sums[sumNum].flags &= ~FLAG_UNFILTERED; ZapHandle((*tocH)->sums[sumNum].cache); fpb->doNotifyThing--; err = euFilterXfered; } } // // do the filtering // if (!err) { // only filter this message if it's not open if ((*tocH)->sums[sumNum].messH == NULL) { err = FilterMessageLo(flkIncoming,tocH,sumNum,fpb,noXfer); (*tocH)->sums[sumNum].flags &= ~FLAG_UNFILTERED; } } dirty = true; // // clean up after // if (err!=euFilterXfered) { // make sure we open the visible mailbox, not the hidden one. if (mailboxspec.name[0] == 0) GetRealIMAPSpec((*tocH)->mailbox.spec, &mailboxspec); // a message survived the filtering process and remained in this mailbox. // open this mailbox if we ought to ... if (mailboxspec.name[0]) { if (!PrefIsSet(PREF_NO_OPEN_IN)) AddSpecToList(&mailboxspec,fpb->mailbox); } // cleanup if ((*tocH)->sums[sumNum].cache) ZapHandle((*tocH)->sums[sumNum].cache); } // make the summary visible or invisible as appropriate if (ShowHideFilteredSummary(tocH, sumNum)) sumNum--; // a message was removed from the toc if (err==euFilterStop||err==euFilterXfered) err = noErr; if (CommandPeriod) break; } // cancel all filtering if the user hit command-period if (CommandPeriod) IMAPFilteringCancelled(true); if (err) { Aprintf(OK_ALRT,Note,THREAD_PUNT_FILTER_ERR,err); NeedToFilterIMAP = false; // give it a rest on error, maybe the user will fix } else { // // filtering completed // SetIMAPMailboxNeeds(TOCToMbox(tocH), kNeedsFilter, false); // Transfer spam to junk mailbox, if we're holding mail in the junk mailbox and // we're running plugins. Be sure to check the correct pers for the settings! PushPers(CurPers); CurPers = TOCToPers(tocH); if (CurPers && JunkPrefBoxHold() && !JunkPrefIMAPNoRunPlugins()) MoveToIMAPJunk(tocH, -1, spamThresh, fpb); PopPers(); } CloseProgress(); FiltersDecRef(); return(err); } #ifdef NEVER /************************************************************************ * NotifyMA - let message assistant know the user wants to filter ************************************************************************/ OSErr NotifyMA(TOCHandle tocH) { AliasHandle maAlias=nil; OSErr err; FSSpec spec; FSSpec setSpec; AEDesc listD, optD; NullADList(&listD,&optD,nil); if (!(err=MABuildDescriptors(tocH,&listD,&optD)) && !(err=CreatorToSpec(MA_CREATOR,&spec))) { GetFileByRef(SettingsRefN,&setSpec); if (!(err=NewAlias(&spec,&setSpec,&maAlias))) { NotifyAHelper(maAlias,&listD,eManFilter,&optD); } } DisposeADList(&listD,&optD,nil); if (err) WarnUser(COULDNT_LAUNCH,err); return(err); } /************************************************************************ * MABuildDescriptors - build descriptors for notifying ************************************************************************/ OSErr MABuildDescriptors(TOCHandle tocH, AEDescPtr listD, AEDescPtr optD) { short mNum; OSErr err; AEDesc messD; OSType messList; /* * build list of messages */ if (!(err=AECreateList(nil,0,False,listD))) { for (mNum=0;mNum<(*tocH)->count;mNum++) { if (err=MessObjFromTOC(tocH,mNum,&messD)) break; if (err=AEPutDesc(listD,0,&messD)) break; } } if (!err && !(err=AECreateList(nil,0,False,optD))) { messList = keyEuMessList; AEPutPtr(optD,1,typeKeyword,&messList,sizeof(messList)); } return(err); } #endif /************************************************************************ * GenSpecWindow - open the spec window ************************************************************************/ void GenSpecWindow(CSpecHandle specList) { short n; short i; Str255 s; Str63 date; MyWindowPtr win, frontWin; WindowPtr winWP, frontWinWP; FSSpec spec; long len; Str255 url; if (!specList || !*specList || !GetHandleSize_(specList)) return; TimeString(LocalDateTime(),False,date,nil); GetRString(s,SPEC_TITLE); /* * find topmost filter window */ // Figure out if the front window is a nag. If so, treat // the nag like a floater. frontWinWP = GetWindowList(); frontWin = GetWindowMyWindowPtr (frontWinWP); for (winWP=frontWinWP;winWP;winWP=GetNextWindow(winWP)) { win = GetWindowMyWindowPtr (winWP); if (IsKnownWindowMyWindow(winWP) && GetWindowKind(winWP)==TEXT_WIN && win->ro && !*(*(TextDHandle)GetWindowPrivateData(winWP))->spec.name) break; } if (!winWP && (win=OpenText(nil,nil,nil,frontWin && frontWin->isNag ? frontWinWP : nil,False,s,True,False))) win->position = (void*)PositionPrefsTitle; if (win) { winWP = GetMyWindowWindowPtr (win); ComposeRString(s,SPEC_INTRO,date); PeteAppendText(s+1,*s,win->pte); n = HandleCount(specList); for (i=0;ipte); PeteAppendText(s+1,*s,win->pte); InsertURLLo(win->pte,len,len+*s-1,MakeFileURL(url,&spec,ProtocolStrn+proCompFile)); PETEMarkDocDirty(PETE,win->pte,False); } win->isDirty = False; win->ro = True; PeteSelect(win,win->pte,0x7fffffff,0x7fffffff); PeteLock(win->pte,0,PeteLen(win->pte),peModLock); PeteKillUndo(win->pte); PeteScroll(win->pte,0,0x7fff); if (!IsWindowVisible (winWP)) { InfiniteClip(GetWindowPort(winWP)); ShowMyWindowBehind(winWP,frontWin && frontWin->isNag ? frontWinWP : nil); } PeteScroll(win->pte,0,0x7fff); } } /************************************************************************ * FilterMessagesAfter - filter messages after a particular spot * NOT IMAP SAFE ************************************************************************/ OSErr FilterMessagesFrom(FilterKeywordEnum fType,TOCHandle tocH,short startWith,FilterPBPtr fpb,Boolean noXfer) { short err; short sumNum; short countWas; MyWindowPtr win; Boolean isOut = (*tocH)->which==OUT; short count; short filterHogTicks = GetRLong(FILTER_HOG_TICKS); uLong startTick = TickCount(); Boolean noInterruptions = false; Boolean deliveryBatch = fType==flkDelivery; Boolean dirty = false; FSSpec inSpec; Boolean isTempIn = (*tocH)->which==IN_TEMP; TOCHandle realInTocH = GetRealInTOC(); if (!realInTocH) return(1); // oops! if ((*tocH)->imapTOC) return(1); inSpec = GetMailboxSpec(realInTocH,-1); // if doing batch filtering but user is idle, do the whole shebang right now if (deliveryBatch) { #ifdef EVER_WANT_MODAL_BG_FILTER if (CheckThreadRunning || (!InBG && (TickCount()-ActiveTicks) < GetRLong(LONG_MODAL_IDLE_SECS)*60)) #endif count = 0; // incremental filtering, don't need count #ifdef EVER_WANT_MODAL_BG_FILTER else { noInterruptions = true; // user is idle; filter all now count = GetDeliveryCount(); } #endif } else count = (*tocH)->count-startWith; if (err=RegenerateFilters()) return(err); if (fType==flkDelivery) fType = flkIncoming; beginFilter: if (AnyFilters(fType)) { if (!deliveryBatch||noInterruptions) { OpenProgress(); } ProgressR(NoBar,count,FILTERING,LEFT_TO_FILTER,nil); for (sumNum=startWith;sumNum<(*tocH)->count;sumNum++) { /* filter the whole darn thing if need more time and we got the idle time we need more time if we didn't filter any messages and we ran out of time */ if (deliveryBatch && !noInterruptions) { if (EventPending()) return kNotEnoughTime; if (TickCount() - startTick > filterHogTicks) { if (dirty) return kNotEnoughTime; } } if (!deliveryBatch) MiniEvents(); #ifdef SPEECH_ENABLED (void) SpeechIdle (); #endif if (CommandPeriod) break; CheckSLIP(); fpb->doNotifyThing++; fpb->notify++; countWas = (*tocH)->count; Progress(NoBar,count--,nil,nil,nil); /* * load up message or cache */ if (isOut) { win = GetAMessage(tocH,sumNum,nil,nil,False); if (!win) err = 1; } else { CacheMessage(tocH,sumNum); if (!(*tocH)->sums[sumNum].cache) { WarnUser(MEM_ERR,MemError()); err = 1; } else HNoPurge((*tocH)->sums[sumNum].cache); } ASSERT(!JunkPrefBoxHold() || (*tocH)->sums[sumNum].spamScore < GetRLong(JUNK_MAILBOX_THRESHHOLD)); /* * do the filtering */ if (!err) err = FilterMessageLo(fType,tocH,sumNum,fpb,noXfer); dirty = true; /* * clean up after */ if (err==euFilterXfered) sumNum--; else { if (isOut) { WindowPtr winWP = GetMyWindowWindowPtr(win); if (winWP && !IsWindowVisible (winWP)) CloseMyWindow(winWP); } else { if ((*tocH)->sums[sumNum].cache) HPurge((*tocH)->sums[sumNum].cache); ASSERT(realInTocH); if ((deliveryBatch || isTempIn) && realInTocH) { if (!(err=MoveMessageLo(tocH, sumNum, &inSpec, false, false, true))) sumNum--; else { WarnUser(WRITE_MBOX,err); // may not want a second alert? maybe put in task progress window? break; } } } } if (err==euFilterStop||err==euFilterXfered) err = noErr; if (CommandPeriod) break; } } /* just transfer messages to in box */ else { if ((deliveryBatch || isTempIn) && realInTocH) { count=(*tocH)->count; if (!deliveryBatch) OpenProgress(); ProgressR(NoBar,count,MOVING_MESSAGES_TO_IN,LEFT_TO_MOVE,nil); for (sumNum=0;sumNum<(*tocH)->count;sumNum++) { /* filter the whole darn thing if need more time and we got the idle time we need more time if we didn't filter any messages and we ran out of time */ fpb->doNotifyThing++; fpb->notify++; if (!noInterruptions) { if (EventPending()) return kNotEnoughTime; if (TickCount() - startTick > filterHogTicks) { if (dirty) return kNotEnoughTime; } } Progress(NoBar,count--,nil,nil,nil); if (CommandPeriod) break; if (!(err=MoveMessageLo(tocH, sumNum, &inSpec, false, false, true))) sumNum--; else { WarnUser(WRITE_MBOX,err); // may not want a second alert? maybe put in task progress window? break; } dirty = true; } } else if (realInTocH) { // just notify fpb->doNotifyThing = count; fpb->notify = count; } } if (deliveryBatch) { if (err) { Aprintf(OK_ALRT,Note,THREAD_PUNT_FILTER_ERR,err); NeedToFilterIn = false; // give it a rest on error, maybe the user will fix } } CloseProgress(); if (realInTocH) BoxFClose(realInTocH,true); FiltersDecRef(); return(err); } /********************************************************************** * FilterMessage - filter a single message, including postprocessing **********************************************************************/ OSErr FilterMessage(FilterKeywordEnum fType,TOCHandle tocH,short sumNum) { FilterPB fpb; OSErr err; InitFPB(&fpb,false,true); err = FilterMessageLo(fType,tocH,sumNum,&fpb,False); if (fType==flkIncoming && err!=euFilterXfered) TransferMenuChoice(TRANSFER_MENU,TRANSFER_IN_ITEM,tocH,sumNum,0,false); FilterPostprocess(fType,&fpb); // Hide the message if it was deleted from an IMAP mailbox. if ((fType == flkManual) && (*tocH)->imapTOC) ShowHideFilteredSummary(tocH, sumNum); return(err); } /************************************************************************ * FilterMessageLo - filter a message; needs to be setup first ************************************************************************/ OSErr FilterMessageLo(FilterKeywordEnum fType,TOCHandle tocH,short sumNum,FilterPBPtr fpb,Boolean noXfer) { short err; Boolean done=False; Str255 title; short oldCount = (*tocH)->count; Boolean oldSensitive = Sensitive; short f; short n; Boolean openIncomingErr=False; Boolean unjunking = fType==flkIncoming && (*tocH)->which==JUNK; Sensitive = False; // do all filtering insensitively NukeXfUndo(); MakeMessTitle(title,tocH,sumNum,True); ProgressMessage(kpMessage,title); CycleBalls(); if (err=RegenerateFilters()) return(err); CacheMessage(tocH,sumNum); if (!(*tocH)->imapTOC) { if (!(*tocH)->sums[sumNum].cache) { WarnUser(GENERAL,MemError()); return(1); } HNoPurge((*tocH)->sums[sumNum].cache); } openIncomingErr = (fType==flkIncoming && PrefIsSet(PREF_OPEN_IN_ERR_MESS) && ((*tocH)->which==IN || (*tocH)->which==IN_TEMP) && (*tocH)->sums[sumNum].state==MESG_ERR); if (AnyFilters(fType) || openIncomingErr) { Handle saveFilters; short filterIdx; InitFPB(fpb,true,false); fpb->tocH = tocH; fpb->sumNum = sumNum; fpb->openMessage = openIncomingErr; saveFilters = Filters; for(filterIdx=0;filterIdx<3 && !err;filterIdx++) { if (!(Filters = filterIdx==0 ? PreFilters : filterIdx==1 ? saveFilters : PostFilters)) continue; n = NFilters; for (f=0;fcount && (*tocH)->sums[sumNum].cache) HPurge((*tocH)->sums[sumNum].cache); // Kill the message cache if we filled it with minimal headers for an IMAP filtering spree if (oldCount==(*tocH)->count && (*tocH)->sums[sumNum].cache && (*tocH)->sums[sumNum].offset < 0) { ZapHandle((*tocH)->sums[sumNum].cache); (*tocH)->sums[sumNum].cache = nil; } Sensitive = oldSensitive; if (err==euFilterStop && unjunking) err = noErr; return(err); } /************************************************************************ * AddSpecToList - add an FSSpec to a list of FSSpecs ************************************************************************/ void AddSpecToList(FSSpecPtr spec,CSpecHandle specList) { short n; CSpec cspec; if (!specList || !*specList) return; if (spec->parID == MailRoot.dirId && EqualStrRes(spec->name,TRASH)) return; n = HandleCount(specList); LDRef(specList); while(n--) if (SameSpec(&(*specList)[n].spec,spec)) { (*specList)[n].count++; UL(specList); return; } UL(specList); cspec.spec = *spec; cspec.count = 1; PtrPlusHand_(&cspec,specList,sizeof(**specList)); } /************************************************************************ * TermMatch - does a message match a term? ************************************************************************/ Boolean TermMatch(MTPtr mt,TOCHandle tocH, short sumNum,FilterPBPtr fpb) { UPtr text; long bodyOffset; UPtr end; UPtr spot; UPtr hEnd; Boolean match = False; long size; Boolean hasColon; Boolean foundHeader = False; UHandle cache = nil; Str31 headerName; /* * match */ // Set things up to do filtering to or in an IMAP mailbox if (!IMAPStartFiltering(tocH, ((*tocH)->imapTOC && ((*tocH)->sums[sumNum].offset==-1)))) { // failed? Warn the user, and stop the filtering process. IMAPError(kIMAPSearching, kIMAPSelectMailboxErr, errIMAPSearchMailboxErr); CommandPeriod = true; return (false); } // if the message to be filtered has not been downloaded if ((*tocH)->imapTOC && ((*tocH)->sums[sumNum].offset==-1)) { // if we're not looking at anything but headers if (mt->headerID!=FILTER_BODY) { // then download the headers and stick 'em in the cache if (!(*tocH)->sums[sumNum].cache) { Handle cache; if ((cache=IMAPFetchMessageHeadersForFiltering(tocH, sumNum)) != NULL) { HPurge(cache); (*tocH)->sums[sumNum].cache = cache; } } // make sure we've got a valid cache to work with if ((*tocH)->imapTOC && (!(*tocH)->sums[sumNum].cache) || !*((*tocH)->sums[sumNum].cache)) { ASSERT(0); // call John now! ComposeLogS(LOG_FILT,nil,"\pMissing Cache! %s sumnum %d uid %d",(*tocH)->mailbox.spec.name, sumNum, (*tocH)->sums[sumNum].uidHash); return (false); } if ((*tocH)->sums[sumNum].cache) { text = LDRef((*tocH)->sums[sumNum].cache); bodyOffset = GetHandleSize((*tocH)->sums[sumNum].cache); } else { text = nil; } } // else // we'll need to search on the server } else { // this is a critical point to verify the cache so that we don't crash if (!(*tocH)->sums[sumNum].cache) { ASSERT(0); // if this is IMAP, call John now! ComposeLogS(LOG_FILT,nil,"\pMissing Cache! %s sumnum %d uid %d",(*tocH)->mailbox.spec.name, sumNum, (*tocH)->sums[sumNum].uidHash); return false; } text = LDRef((*tocH)->sums[sumNum].cache); bodyOffset = (*tocH)->sums[sumNum].bodyOffset; } // make sure we've got a valid cache to work with if ((*tocH)->imapTOC && (!(*tocH)->sums[sumNum].cache) || !*((*tocH)->sums[sumNum].cache)) { ASSERT(0); // call John now! ComposeLogS(LOG_FILT,nil,"\pMissing Cache! %s sumnum %d uid %d",(*tocH)->mailbox.spec.name, sumNum, (*tocH)->sums[sumNum].uidHash); return (false); } if (*mt->header && mt->headerID!=FILTER_BODY) /* we DO have a header to look for */ { if (!striscmp(mt->header+1,"date")) match = TermDateMatch(mt,tocH,sumNum); else if (HasFeature(featureJunk) && EqualStrRes(mt->header,FiltMetaEnglishStrn+fmeJunk)) { UseFeature(featureJunk); match = TermJunkMatch(mt,tocH,sumNum); } else if (EqualStrRes(mt->header,FiltMetaEnglishStrn+fmePersonality)) { match = TermPersMatch(mt,tocH,sumNum); } else if (FromIntersectNickFile(mt,tocH,sumNum)) // performance special case { match = FromIntersectNickFileMatch(mt,tocH,sumNum); } // if this is an IMAP message that hasn't been downloaded, look on the server else if ((*tocH)->imapTOC && ((*tocH)->sums[sumNum].offset==-1) && !text) { match = IMAPTermMatch(mt, &(*tocH)->sums[sumNum]); } else { hasColon = nil!=strchr(mt->header+1,':'); end = text + bodyOffset; size = end-text; for (spot=FindHeaderString(text,mt->header,&size,False); spot; spot=FindHeaderString(spot,mt->header,&size,False)) { /* we found one */ foundHeader = True; switch (mt->verb) { case mbmAppears: match = True; goto done; break; case mbmNotAppears: match = False; goto done; break; } /* find last char of header */ for (hEnd=spot;hEndwhich==OUT || ((*tocH)->sums[sumNum].flags&FLAG_OUT))) { UPtr hnStart,hnEnd; short hid; UHandle addrs=nil; for (hnEnd=spot;hnEnd>text&&*hnEnd!=':';hnEnd--); for (hnStart=hnEnd;hnStart>text&&hnStart[-1]!='\015';hnStart--); MakePStr(headerName,hnStart,hnEnd-hnStart+1); if (StringSame(headerName,fpb->cc)) hid = CC_HEAD; else if (StringSame(headerName,fpb->bcc)) hid = BCC_HEAD; else if (StringSame(headerName,fpb->to)) hid = TO_HEAD; else hid = 0; if (hid) { switch(hid) { case CC_HEAD: addrs = fpb->ccAddresses; break; case TO_HEAD: addrs = fpb->toAddresses; break; case BCC_HEAD: addrs = fpb->bccAddresses; break; default: ZapHandle(addrs); break; } match = TermExpMatch(mt,spot,hEnd,&addrs); if (addrs) switch(hid) { case CC_HEAD: fpb->ccAddresses = addrs; break; case TO_HEAD: fpb->toAddresses = addrs; break; case BCC_HEAD: fpb->bccAddresses = addrs; break; default: ZapHandle(addrs); break; } } } if (match) { switch (mt->verb) { case mbmIsnt: case mbmNotContains: case mbmNotIntersects: case mbmNotIntersectsFile: match=False; } goto done; } spot = hEnd+1; size = end-spot; } match = mt->verb==mbmIsnt||mt->verb==mbmNotContains||mt->verb==mbmNotAppears||mt->verb==mbmNotIntersects||mt->verb==mbmNotIntersectsFile; } } else { // if this is an IMAP message that hasn't been downloaded, check the server. if ((*tocH)->imapTOC && ((*tocH)->sums[sumNum].offset==-1)) { match = IMAPTermMatch(mt, &(*tocH)->sums[sumNum]); } else { spot = text + bodyOffset + 1; end = text + GetHandleSize((*tocH)->sums[sumNum].cache); match = TermPtrMatch(mt,spot,end); switch (mt->verb) { case mbmIsnt: case mbmNotContains: case mbmNotIntersects: case mbmNotIntersectsFile: match=!match; } } } done: UL((*tocH)->sums[sumNum].cache); return(match); } /********************************************************************** * **********************************************************************/ Boolean TermExpMatch(MTPtr mt, UPtr spot, UPtr end,UHandle *cache) { BinAddrHandle raw=nil; BinAddrHandle expanded=nil; Boolean result = False; if (cache) { if (*cache) if (!**cache) ZapHandle(*cache); else { expanded = *cache; HNoPurge(expanded); } } if (!expanded) if (!SuckPtrAddresses(&raw,spot,end-spot,True,False,False,nil)) if (!ExpandAliases(&expanded,raw,0,True)) FlattenListWith(expanded,','); if (expanded) { LDRef(expanded); result = TermPtrMatch(mt,*expanded,*expanded+GetHandleSize(expanded)); UL(expanded); if (cache) { //HPurge(expanded); *cache = expanded; expanded = nil; } } ZapHandle(raw); ZapHandle(expanded); return(result); } /************************************************************************ * DoesIntersectNick - does a string intersect a nickname? ************************************************************************/ Boolean DoesIntersectNick(UHandle nickAddresses,UHandle nickExpanded,UPtr spot,long len) { UHandle addresses = nil; Boolean match = False; UPtr nick,addr; OSErr err = SuckPtrAddresses(&addresses,spot,len,False,False,False,nil); if (err>0 || err==paramErr) return(False); // syntax error in header; intersects won't work if (!(FGlobalErr=err)) { for (addr = LDRef(addresses); *addr; addr += *addr + 2) { for (nick=LDRef(nickAddresses); *nick; nick += *nick + 2) if (match=StringSame(nick,addr)) goto done; for (nick=LDRef(nickExpanded); *nick; nick += *nick + 2) if (match=StringSame(nick,addr)) goto done; } } done: ZapHandle(addresses); return(match); } /************************************************************************ * FromIntersectNickFile - Is this the special case of matching from address * against nickname file? ************************************************************************/ Boolean FromIntersectNickFile(MTPtr mt, TOCHandle tocH,short sumNum) { return (mt->verb==mbmIntersectsFile || mt->verb==mbmNotIntersectsFile) && !striscmp(mt->header+1,"from") && ValidHash((*tocH)->sums[sumNum].fromHash); } /************************************************************************ * FromIntersectNickFileMatch - Special case routine for matching from address * against nickname file ************************************************************************/ Boolean FromIntersectNickFileMatch(MTPtr mt, TOCHandle tocH,short sumNum) { PStr file = nil; Boolean match; if (*mt->value && !EqualStrRes(mt->value,ANY_ALIAS_FILE)) file = mt->value; match = HashAppearsInAliasFile((*tocH)->sums[sumNum].fromHash,file); if (mt->verb==mbmNotIntersectsFile) match = !match; return match; } /************************************************************************ * DoesIntersectNickFile - does a string intersect a nickname file? ************************************************************************/ Boolean DoesIntersectNickFile(PStr file,UPtr spot,long len) { UHandle addresses = nil; Boolean match = False; UPtr addr; OSErr err = SuckPtrAddresses(&addresses,spot,len,False,False,False,nil); if (file && (!*file || EqualStrRes(file,ANY_ALIAS_FILE))) file = nil; // tells stuff below to match all files if (err>0 || err==paramErr) return(False); // syntax error in header; intersects won't work if (!(FGlobalErr=err)) { for (addr = LDRef(addresses); *addr; addr += *addr + 2) if (match=AppearsInAliasFile(addr,file)) break; } done: ZapHandle(addresses); return(match); } /************************************************************************ * TermPtrMatch - does the term match a particular string? ************************************************************************/ Boolean TermPtrMatch(MTPtr mt,UPtr spot,UPtr end) { Boolean match=False; switch(mt->verb) { case mbmRegEx: if (!mt->regex) { PtoCcpy(P1,mt->value); mt->regex = regcomp(P1); } if (mt->regex) { match = 0 <= SearchRegExpPtr(LDRef(mt->regex),spot,0,end-spot); UL(mt->regex); } break; case mbmNotContains: case mbmContains: match = !PrefIsSet(PREF_NO_FILT_LWSP) ? PPtrMatchLWSP(mt->value,spot,end-spot,false,false) : nil!=PPtrFindSub(mt->value,spot,end-spot); break; case mbmIsnt: case mbmIs: if (!PrefIsSet(PREF_NO_FILT_LWSP)) match = PPtrMatchLWSP(mt->value,spot,end-spot,true,true); else if (end-spot != *mt->value) match=False; else match = 0==strincmp(mt->value+1,spot,*mt->value); break; case mbmStarts: if (!PrefIsSet(PREF_NO_FILT_LWSP)) match = PPtrMatchLWSP(mt->value,spot,end-spot,true,false); else if (end-spot<*mt->value) match = False; else match = 0==strincmp(mt->value+1,spot,*mt->value); break; case mbmEnds: if (!PrefIsSet(PREF_NO_FILT_LWSP)) match = PPtrMatchLWSP(mt->value,spot,end-spot,false,true); else if (end-spot<*mt->value) match = False; else match = 0==strincmp(mt->value+1,end-*mt->value,*mt->value); break; case mbmNotAppears: case mbmAppears: match = True; break; case mbmNotIntersects: case mbmIntersects: { /* * cache nickname expansion */ if (!mt->nickExpanded || !*mt->nickExpanded || !mt->nickAddresses || !*mt->nickAddresses) { ZapHandle(mt->nickExpanded); ZapHandle(mt->nickAddresses); if (!(FGlobalErr=SuckPtrAddresses(&mt->nickAddresses,mt->value+1,*mt->value,False,False,False,nil))) FGlobalErr=ExpandAliases(&mt->nickExpanded,mt->nickAddresses,0,False); } /* * did we get them? */ if (mt->nickExpanded && mt->nickAddresses) { HNoPurge(mt->nickExpanded); HNoPurge(mt->nickAddresses); } else return(False); /* * test it */ match = DoesIntersectNick(mt->nickAddresses,mt->nickExpanded,spot,end-spot); /* * allow it to be purged if need be */ HPurge(mt->nickExpanded); HPurge(mt->nickAddresses); UL(mt->nickExpanded); UL(mt->nickAddresses); } break; case mbmNotIntersectsFile: case mbmIntersectsFile: { /* * test it */ match = DoesIntersectNickFile(mt->value,spot,end-spot); } break; default: ASSERT(1); break; } return(match); } /************************************************************************ * TermDateMatch - does the date of a message match a term? ************************************************************************/ Boolean TermDateMatch(MTPtr mt, TOCHandle tocH, short sumNum) { Str255 s; Boolean match; ComputeLocalDate(&(*tocH)->sums[sumNum],s); match = TermPtrMatch(mt,s+1,s+*s+1); if (mt->verb==mbmIsnt || mt->verb==mbmNotContains) match = !match; return(match); } /************************************************************************ * TermPersMatch - does the personality of a message match a term? ************************************************************************/ Boolean TermPersMatch(MTPtr mt, TOCHandle tocH, short sumNum) { Str255 s; Boolean match; PCopy(s,(*PERS_FORCE(TS_TO_PERS(tocH,sumNum)))->name); if (!*s) GetRString(s,DOMINANT); match = TermPtrMatch(mt,s+1,s+*s+1); if (mt->verb==mbmIsnt || mt->verb==mbmNotContains) match = !match; return(match); } /************************************************************************ * TermJunkMatch - does the junk score of a message match a term? ************************************************************************/ Boolean TermJunkMatch(MTPtr mt, TOCHandle tocH, short sumNum) { long num; StringToNum(mt->value,&num); switch (mt->verb) { case mbmIs: return (*tocH)->sums[sumNum].spamScore == num; break; case mbmIsnt: return (*tocH)->sums[sumNum].spamScore != num; break; case mbmJunkMore: return (*tocH)->sums[sumNum].spamScore > num; break; case mbmJunkLess: return (*tocH)->sums[sumNum].spamScore < num; break; default: return false; break; } } /************************************************************************ * TermPriorMatch - does the priority of a message match a term? ************************************************************************/ Boolean TermPriorMatch(MTPtr mt, TOCHandle tocH, short sumNum) { Str255 s; Boolean match; short priority; priority = (*tocH)->sums[sumNum].priority; priority = Prior2Display(priority); if (priority==3) return(mt->verb==mbmIsnt || mt->verb==mbmNotContains); PriorityHeader(s,priority); match = TermPtrMatch(mt,s+1,s+*s+1); if (mt->verb==mbmIsnt || mt->verb==mbmNotContains) match = !match; return(match); } /************************************************************************ * AnyFiltersLo - do any filters match the given filtertype? ************************************************************************/ Boolean AnyFiltersLo(FilterKeywordEnum fType,Handle hFilters) { Handle saveFilters = Filters; Boolean result = false; short n; short f; Filters = hFilters; n = NFilters; for (f=0;fsums[sumNum].cache; if (cache) { HNoPurge(cache); Zero(fpb); match = FilterMatch(f,tocH,sumNum,&fpb); ZapHandle(fpb.ccAddresses); ZapHandle(fpb.bccAddresses); ZapHandle(fpb.toAddresses); HPurge(cache); // Purge the message cache if we filled it with minimal headers for an IMAP filtering spree if ((*tocH)->sums[sumNum].cache && (*tocH)->sums[sumNum].offset < 0) { ZapHandle((*tocH)->sums[sumNum].cache); (*tocH)->sums[sumNum].cache = nil; } } return(match); } /************************************************************************ * FilterMatch - does a message match a filter? ************************************************************************/ Boolean FilterMatch(short filter,TOCHandle tocH,short sumNum,FilterPBPtr fpb) { MatchTerm term; Boolean match; Boolean result; term = FR[filter].terms[0]; match = *term.header ? TermMatch(&term,tocH,sumNum,fpb) : false; FR[filter].terms[0] = term; //store back any cached info if (FGlobalErr) result = False; else { term = FR[filter].terms[1]; if (!*term.header) result = match; else switch(FR[filter].conjunction) { case cjIgnore: result = match; break; case cjAnd: if (!match) result = False; else result = TermMatch(&term,tocH,sumNum,fpb); break; case cjOr: if (match) result = True; else result = TermMatch(&term,tocH,sumNum,fpb); break; case cjUnless: if (!match) result = False; else result = !TermMatch(&term,tocH,sumNum,fpb); break; } FR[filter].terms[1] = term; //store back any cached info } if (result && (LogLevel&LOG_FILT)) FiltLogMatch(filter,tocH,sumNum); // Purge the message cache if we filled it with minimal headers for an IMAP filtering spree. // Note, only do this if we successfully matched. Otherwise, we'll take care of it when filtering is through. if (result && (*tocH)->sums[sumNum].cache && (*tocH)->sums[sumNum].offset < 0) { ZapHandle((*tocH)->sums[sumNum].cache); (*tocH)->sums[sumNum].cache = nil; } return(result); } /********************************************************************** * **********************************************************************/ void FiltLogMatch(short filter,TOCHandle tocH,short sumNum) { Str31 name; Str255 title; PSCopy(name,FR[filter].name); MakeMessTitle(title,tocH,sumNum,True); ComposeLogR(LOG_FILT,nil,FILT_LOG_FMT,name,title); } /************************************************************************ * NonSequitur - change the subject ************************************************************************/ void NonSequitur(PStr subject, TOCHandle tocH, short sumNum) { Str255 newSub; Str127 oldSub; Str63 replace; Str31 brackets; UPtr ampr; GetRString(brackets,SUBJ_TRIM_STR); GetRString(replace,SUBJ_REPLACE); PCopy(oldSub,(*tocH)->sums[sumNum].subj); if (ampr = PPtrFindSub(replace,subject+1,*subject)) { MakePStr(newSub,subject+1,ampr-subject-1); if (StartsWith(subject,brackets)) { BMD(newSub+1+*brackets,newSub+1,*newSub-*brackets); *newSub -= *brackets; TrimSquares(oldSub,true,true); TrimAllWhite(oldSub); TrimInternalWhite(oldSub); } PSCat(newSub,oldSub); *ampr = *subject-(ampr-subject); PSCat(newSub,ampr); } else PSCopy(newSub,subject); SetSubject(tocH,sumNum,newSub); } /********************************************************************** * FilterLastMatch - return the time a filter was last used **********************************************************************/ uLong FilterLastMatch(short filter) { FUHandle fuh = GetResource_(FU_TYPE,FU_ID); uLong when = 0; FUPtr fup; long id = FR[filter].fu.id; if (!fuh || !id) return(0); /* not known */ for (fup=(FUPtr)LDRef(fuh) + GetHandleSize_(fuh)/sizeof(FilterUse)-1;fup>=*fuh;fup--) { if (fup->id==id) { when = fup->lastMatch; break; } } UL(fuh); return(when); } /********************************************************************** * FilterLastMatchHi - return the time a filter was last used, using cache if there **********************************************************************/ uLong FilterLastMatchHi(short filter) { uLong secs; if (FR[filter].fu.lastMatch) return(FR[filter].fu.lastMatch); if (secs = FilterLastMatch(filter)) FR[filter].fu.lastMatch = secs; return(secs); } /********************************************************************** * FilterNoteMatch - note that a filter has matched **********************************************************************/ OSErr FilterNoteMatch(short filter, long secs) { FUHandle fuh = GetResource_(FU_TYPE,FU_ID); FUPtr fup; OSErr err=fnfErr; FilterUse fu; long id = FR[filter].fu.id; FR[filter].fu.lastMatch = secs; if (id) { /* * if resource doesn't exist, add it */ if (!fuh) { fuh = NuHandle(0); if (!fuh) return(MemError()); AddMyResource_(fuh,FU_TYPE,FU_ID,""); if (err=ResError()) return(err); } /* * find existing record */ HNoPurge_(fuh); err = fnfErr; for (fup=(FUPtr)LDRef(fuh) + GetHandleSize_(fuh)/sizeof(FilterUse)-1;fup>=*fuh;fup--) { if (fup->id==id) { fup->lastMatch = secs; err = noErr; } } UL(fuh); /* * if didn't find, add */ if (err==fnfErr) { fu.id = id; fu.lastMatch = secs; err = PtrPlusHand_(&fu,fuh,sizeof(fu)); } /* * make sure it gets written out sometime */ ChangedResource((Handle)fuh); } return(err); } /************************************************************************ * TakeFilterAction - take an action that has been deemed necessary ************************************************************************/ OSErr TakeFilterAction(short filter,FilterPBPtr fpb,Boolean noXfer) { short err = noErr; FActionHandle fa; short pass; FilterNoteMatch(filter,GMTDateTime()); for (pass=0;passnext) { if (FAPass((*fa)->action)==pass && !(noXfer && (*fa)->action==flkTransfer)) { err = CallAction(faeDo,fa,nil,fpb); } } } return(err); } #endif