1 line
48 KiB
C
Executable File
1 line
48 KiB
C
Executable File
/* Copyright (c) 2017, Computer History Museum
|
|
All rights reserved.
|
|
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to
|
|
the limitations in the disclaimer below) provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
|
disclaimer in the documentation and/or other materials provided with the distribution.
|
|
* Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE
|
|
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
DAMAGE. */
|
|
|
|
#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;(i<end);i++)
|
|
if (((*source)->sums[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;(sumNum<end) && i;sumNum++)
|
|
if (((*source)->sums[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 <CR>
|
|
// 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 <CR><CR> 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);
|
|
}
|