eudora-mac/message.c

1 line
147 KiB
C
Executable File
Raw Permalink Blame History

/* 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 "message.h"
#define FILE_NUM 25
/* Copyright (c) 1990-1992 by the University of Illinois Board of Trustees */
#pragma segment Message
UHandle GetMessText(MessHandle messH);
int MessErr;
#ifndef IMAP
void MovingAttachments(TOCHandle tocH,short sumNum,Boolean attach,Boolean wipe,Boolean nuke,Boolean IMAPStubsOnly);
#endif
int FindAndCopyHeader(MessHandle origMH,MessHandle newMH,UPtr fromHead,short toHead);
void WeedHeaders(UHandle buffer,long *weeded,short toWeed,AccuPtr weeds);
#ifdef IMAP
MyWindowPtr OpenMessage(TOCHandle tocH,short sumNum,WindowPtr winWP,MyWindowPtr win, Boolean showIt, Boolean preview);
#else
MyWindowPtr OpenMessage(TOCHandle tocH,short sumNum,WindowPtr winWP,MyWindowPtr win, Boolean showIt);
#endif
OSErr RemoveSelf(MessHandle messH,short head,Boolean wantErrors);
void FindFrom(UPtr who, PETEHandle pte);
void Attribute(short attrId,MessHandle origMessH,MessHandle newMessH,Boolean atEnd);
void XferCustomTable(MessHandle origMessH,MessHandle newMessH);
void WeedXAttachments(MessHandle messH,Boolean errReport);
void RemoveIndAttachment(MessHandle messH, short index);
void PeteApplyStyles(PETEHandle pte,OffsetAndStyleHandle styles);
OSErr CopyToOut(TOCHandle fromTocH,short sumNum,TOCHandle toTocH);
OSErr UniqueHeader(MessHandle messH,short head,Boolean wantErrors);
OSErr FindMessageByMID(uLong mid,TOCHandle *tocH,short *sumNum);
OSErr WipeMessage(TOCHandle tocH,short sumNum);
long StripTrailingNewlines(Handle buffer,long stop);
OSErr MessageWarnings(TOCHandle tocH, short sumNum,Boolean toTrash,Boolean nuke,
Boolean *queuedWarning,Boolean *unsentWarning,Boolean *unreadWarning,Boolean *busyWarning);
OSErr SelectedWarnings(TOCHandle tocH,Boolean toTrash,Boolean nuke);
OSErr SingleWarnings(TOCHandle tocH,short sumNum,Boolean toTrash,Boolean nuke);
void DeleteMessageLo(TOCHandle tocH, int sumNum,Boolean nuke);
long CompBodyOffset(MessHandle messH);
OSErr SpoolAttachments(MessHandle messH);
OSErr CopyAttachments(MessHandle messH);
OSErr ReplyReferences(MessHandle origMessH,MessHandle newMessH);
void HTMLifyText(MyWindowPtr win,Handle text);
Boolean DoMessageMenu(short item,TOCHandle tocH,short sumNum,short toWhom,TextAddrHandle addr,long modifiers,Boolean nuke,Boolean *busy);
OSErr CopyNewsgroups(MessHandle origMH,MessHandle newMH);
Boolean AttStillInFolder(FSSpecPtr att, FSSpecPtr folder);
PStr MessCurAddr(MyWindowPtr win, PStr addr);
void DoCFStringGlobalReplace(CFMutableStringRef theString, CFStringRef stringToFind, CFStringRef replacement);
CFStringRef MakeCFMessTitle(TOCHandle tocH,int sumNum);
/************************************************************************
* GetAMessage - grab a message
************************************************************************/
MyWindowPtr GetAMessageLo(TOCHandle origTocH,int origSumNum,WindowPtr winWP, MyWindowPtr win, Boolean showIt, Boolean *newWin)
{
WindowPtr messWinWP;
MessHandle messH;
TOCHandle tocH;
short sumNum;
if(newWin) *newWin = true;
tocH = GetRealTOC(origTocH,origSumNum,&sumNum);
if (!tocH || (*tocH)->count<=sumNum) return(nil);
if (messH = (*tocH)->sums[sumNum].messH)
{
messWinWP = GetMyWindowWindowPtr ((*messH)->win);
if(newWin) *newWin = false;
if (showIt)
{
if (!IsWindowVisible (messWinWP))
ShowMyWindow(messWinWP);
UserSelectWindow(messWinWP);
}
UsingWindow(messWinWP);
win = (*messH)->win;
}
else if ((*tocH)->which==OUT)
win = OpenComp(tocH,sumNum,winWP,win,showIt,False);
#ifdef NEVER
else if (IsALink(tocH,sumNum))
win = OpenLink(tocH,sumNum,winWP,win,showIt);
#endif
else
#ifdef IMAP
win = OpenMessage(tocH,sumNum,winWP,win,showIt,false);
#else
win = OpenMessage(tocH,sumNum,winWP,win,showIt);
#endif
if (win)
{
(*Win2MessH(win))->openedFromTocH = origTocH;
(*Win2MessH(win))->openedFromSerialNum = (*origTocH)->sums[origSumNum].serialNum;
}
return(win);
}
/**********************************************************************
* OpenMessage - open a message in its own window
**********************************************************************/
#ifdef IMAP
MyWindowPtr OpenMessage(TOCHandle tocH,short sumNum,WindowPtr winWP,MyWindowPtr win, Boolean showIt, Boolean preview)
#else
MyWindowPtr OpenMessage(TOCHandle tocH,short sumNum,WindowPtr winWP,MyWindowPtr win, Boolean showIt)
#endif
{
MessHandle messH;
Boolean turvy = showIt && (CurrentModifiers()&optionKey)!=0;
UHandle text=nil;
short ezOpenSum;
PETEDocInitInfo pdi;
OSErr err;
long size;
long partial = GetRLong(PETE_NIBBLE) K;
StackHandle stack;
Boolean useLizzie,disableButtons = false;
PETETextStyle style;
CycleBalls();
if (!(tocH = GetRealTOC(tocH,sumNum,&sumNum)))
return nil;
if ((messH = (MessHandle)NuHandleClear(sizeof(MessType)))==nil)
return(nil);
win = GetNewMyWindow(MESSAGE_WIND,winWP,win,BehindModal,False,False,MESS_WIN);
if (!win)
{
ZapHandle(messH);
return(nil);
}
winWP = GetMyWindowWindowPtr (win);
(*tocH)->sums[sumNum].messH = messH;
(*messH)->win = win;
(*messH)->sumNum = sumNum;
(*messH)->tocH = tocH;
/* apply FLAG_OUT ex post facto */
if ((*tocH)->sums[sumNum].state==SENT || (*tocH)->sums[sumNum].state==UNSENT)
(*tocH)->sums[sumNum].flags |= FLAG_OUT;
SetMyWindowPrivateData(win, (long)messH);
win->close = MessClose;
LL_Push(MessList,messH);
if (MessOptIsSet(messH,OPT_WRITE)) ClearMessOpt(messH,OPT_WRITE);
#ifdef IMAP
// Actually go fetch this message if we must
if ((*tocH)->imapTOC && !IMAPMessageDownloaded(tocH,sumNum))
{
// threading is off. Download message now
if (PrefIsSet(PREF_THREADING_OFF) || !ThreadsAvailable())
{
if (EnsureMsgDownloaded(tocH,sumNum,true))
text = GetMessText(messH);
}
else
{
Str255 scratch;
Boolean reallyGetIt = !preview || (!Offline && AutoCheckOK());
// start the message download thread. If we're opening this message to be previewed ONLY, don't fetch it if we're offline.
err = noErr;
if (!IMAPMessageBeingDownloaded(tocH, sumNum) && !IMAPMessageDownloaded(tocH, sumNum) && reallyGetIt) err = UIDDownloadMessage(tocH, (*tocH)->sums[sumNum].uidHash, false, false);
if (err) goto Abort;
// put the "waiting for download" text in the message window.
if (!reallyGetIt) GetRString(scratch,IMAP_GETTING_MESSAGE_OFFLINE);
else GetRString(scratch,IMAP_GETTING_MESSAGE);
text = PStr2Handle(scratch);
disableButtons = true;
}
}
else
#endif
text = GetMessText(messH);
useLizzie = false;
DefaultPII(win,0==((*tocH)->sums[sumNum].opts&OPT_WRITE),useLizzie?peGrowBox:peVScroll|peGrowBox,&pdi);
pdi.inParaInfo.paraFlags = (!PrefIsSet(PREF_SEND_ENRICHED_NEW) ? 0:peTextOnly);
pdi.docWidth = MessWi(win)-(useLizzie?0:PETE_SCROLLY_DIFFERENCE)+9;
err=PeteCreate(win,&win->pte,0,&pdi);
CleanPII(&pdi);
if (err || !text) goto Abort;
TheBody = win->pte;
if ( MessFlagIsSet ( messH, FLAG_FIXED_WIDTH )) {
short fixedID, fixedSize;
#ifdef USEFIXEDDEFAULTFONT
fixedSize = FixedSize;
fixedID = FixedID;
#else
// what size to use?
fixedSize = GetRLong(DEF_FIXED_SIZE);
if (!fixedSize) fixedSize = FontSize;
// what font to use?
fixedID = GetFontID(GetRString(s,FIXED_FONT));
if (fixedID==applFont)
fixedID = (ScriptVar(smScriptMonoFondSize)>>16)&0xffff;
#endif
PeteFontAndSize(TheBody, fixedID, fixedSize);
}
{
StackInit(sizeof(PartDesc),&stack);
(*PeteExtra(win->pte))->partStack = stack;
PeteCalcOff(win->pte);
if (!showIt||MessIsRich(messH)||MessOptIsSet(messH,OPT_CHARSET)) partial = 0;
size = GetHandleSize(text);
if (!partial || size<partial)
{
// stick in the text
if (!MessFlagIsSet(messH,FLAG_SHOW_ALL) &&
(MessFlagIsSet(messH,FLAG_RICH)
#ifndef OLDHTML
|| MessOptIsSet(messH,OPT_HTML)
#endif
|| MessOptIsSet(messH,OPT_FLOW)
|| MessOptIsSet(messH,OPT_CHARSET)
))
{
err = InsertRichLo(text,0,-1,-1,True,True,TheBody,stack,messH,MessOptIsSet(messH,OPT_DELSP));
ZapHandle(text);
}
else
err = PETEInsertPara(PETE,TheBody,kPETELastPara,nil,text,nil);
if (!err)
{
text = nil; // so we don't dispose of it
PeteTrimTrailingReturns(TheBody,true);
}
}
else
{
err = PETEInsertTextPtr(PETE,win->pte,kPETELastPara,LDRef(text),partial,nil);
UL(text);
}
}
if (err)
{
if (err != userCanceledErr)
WarnUser(PETE_ERR,err);
}
else
{
CFStringRef cfTitle;
cfTitle = MakeCFMessTitle(tocH, sumNum);
SetWindowTitleWithCFString(winWP, cfTitle);
CFRelease(cfTitle);
win->menu = MessMenu;
win->gonnaShow = MessGonnaShow;
win->position = MessagePosition;
win->cursor = MessCursor;
win->button = CompButton; /* it will do */
win->app1 = MessApp1;
win->find = MessFind;
win->curAddr = MessCurAddr;
#ifdef OLDHTML
/*
* interpret rich text
*/
if (!MessFlagIsSet(messH,FLAG_SHOW_ALL))
{
if (MessOptIsSet(messH,OPT_HTML))
PeteHTML(TheBody,SumOf(messH)->bodyOffset-(*messH)->weeded+1,0,True);
}
#endif
if (showIt)
{
/*
* prepare for easy open
*/
ezOpenSum = EzOpenFind(tocH,sumNum);
if (ShowMyWindow(winWP)) {return(nil);}
/*
* scroll to show first line of body
*/
ShowMessageSeparator(TheBody,true);
PeteFocus(win,TheBody,True);
/*
* rest of the text?
*/
if (text && !useLizzie)
{
PeteCalcOff(win->pte);
err = PETEInsertTextPtr(PETE,win->pte,partial,LDRef(text)+partial,size-partial,nil);
PeteSmallParas(win->pte);
PeteCalcOn(win->pte);
ZapHandle(text);
PeteSetURLRescan(win->pte,0);
if (err)
{
if (err != userCanceledErr)
WarnUser(PETE_ERR,err);
}
}
/*
* prepare for ezopen
*/
if (!err && ezOpenSum >= 0)
{
(*messH)->ezOpenSerialNum = (*tocH)->sums[ezOpenSum].serialNum;
CacheMessage(tocH,ezOpenSum);
}
if (disableButtons)
EnableMsgButtons(win,false);
}
}
style.tsLock = peModLock;
PETESetTextStyle(PETE,TheBody,0,0x7fffffff,&style,peLockValid);
PETEMarkDocDirty(PETE,TheBody,False);
win->isDirty = False;
if (err)
{
Abort:
PeteCleanList(win->pte);
win->isDirty = False;
CloseMyWindow(winWP);
win = nil;
if (text) ZapHandle(text);
}
return(win);
}
#pragma segment Message
/**********************************************************************
* MessCurAddr - return the address most closely associated with this message
**********************************************************************/
PStr MessCurAddr(MyWindowPtr win, PStr addr)
{
MessHandle messH = Win2MessH(win);
extern PStr CompCurAddr(MyWindowPtr win, PStr addr);
*addr = 0;
if (messH)
{
if (MessFlagIsSet(messH,FLAG_OUT))
CompCurAddr(win,addr);
else
{
if (win->hasSelection) return CurAddrSel(win,addr);
else
{
SuckHeaderText(messH,addr,sizeof(Str255),FROM_HEAD);
ShortAddr(addr,addr);
}
}
}
return *addr ? addr : nil;
}
/**********************************************************************
* MakeMessTitle - make a reasonable message title from a summary
**********************************************************************/
void MakeMessTitle(UPtr title,TOCHandle tocH,int sumNum, Boolean useSummary)
{
Str63 from, date, time, mailbox, subject;
Str63 pattern;
Str63 datetime;
long secs;
Str31 zoneStr;
long zone;
PCopy(from,(*tocH)->sums[sumNum].from);
if (useSummary) *from = MIN(*from,(*BoxWidths)[blFrom-1]);
if (useSummary)
{
if ((*BoxWidths)[blDate-1]>1)
{
ComputeLocalDate((*tocH)->sums+sumNum,datetime);
*datetime = MIN(*datetime,(*BoxWidths)[blDate-1]);
}
else *datetime = 0;
}
else
{
if (secs = (*tocH)->sums[sumNum].seconds)
{
zone = PrefIsSet(PREF_LOCAL_DATE) ? ZoneSecs() : 60*(*tocH)->sums[sumNum].origZone;
secs += zone;
FormatZone(zoneStr,zone);
TimeString(secs,False,time,nil);
DateString(secs,shortDate,date,nil);
utl_PlugParams(GetRString(pattern,DATE_SUM_FMT),datetime,date,time,zoneStr,"");
}
else *datetime = 0;
}
GetMailboxName(tocH,sumNum,mailbox);
PCopy(subject,(*tocH)->sums[sumNum].subj);
utl_PlugParams(GetRString(pattern,MESS_TITLE_PLUG),title,mailbox,from,datetime,subject);
}
void DoCFStringGlobalReplace(CFMutableStringRef theString, CFStringRef stringToFind, CFStringRef replacement)
{
CFArrayRef ranges;
CFRange wholeString, *range;
CFIndex i;
wholeString.location = 0;
wholeString.length = CFStringGetLength(theString);
ranges = CFStringCreateArrayWithFindResults(NULL, theString, stringToFind, wholeString, 0);
if(!ranges) return;
for(i = CFArrayGetCount(ranges); --i >= 0; )
{
range = (CFRange *)CFArrayGetValueAtIndex(ranges, i);
if(replacement != NULL)
CFStringReplace(theString, *range, replacement);
else
CFStringDelete(theString, *range);
}
CFRelease(ranges);
}
CFStringRef MakeCFMessTitle(TOCHandle tocH,int sumNum)
{
CFStringRef temp;
CFMutableStringRef pattern;
CFRange wholeString;
static CFStringRef upZero = NULL, upOne = NULL, upTwo = NULL, upThree = NULL;
Str63 tempS;
long secs;
long zone;
if(!upZero) upZero = CFSTR("^0");
if(!upOne) upOne = CFSTR("^1");
if(!upTwo) upTwo = CFSTR("^2");
if(!upThree) upThree = CFSTR("^3");
pattern = CFStringCreateMutable(NULL, 0);
if (secs = (*tocH)->sums[sumNum].seconds)
{
GetRString(tempS,DATE_SUM_FMT);
CFStringAppendPascalString(pattern, tempS, CFStringGetSystemEncoding());
zone = PrefIsSet(PREF_LOCAL_DATE) ? ZoneSecs() : 60*(*tocH)->sums[sumNum].origZone;
secs += zone;
FormatZone(tempS,zone);
temp = CFStringCreateWithPascalString(NULL, tempS, CFStringGetSystemEncoding());
DoCFStringGlobalReplace(pattern, upTwo, temp);
CFRelease(temp);
TimeString(secs,False,tempS,nil);
temp = CFStringCreateWithPascalString(NULL, tempS, CFStringGetSystemEncoding());
DoCFStringGlobalReplace(pattern, upOne, temp);
CFRelease(temp);
DateString(secs,shortDate,tempS,nil);
temp = CFStringCreateWithPascalString(NULL, tempS, CFStringGetSystemEncoding());
DoCFStringGlobalReplace(pattern, upZero, temp);
CFRelease(temp);
}
temp = CFStringCreateCopy(NULL, pattern);
wholeString.location = 0;
wholeString.length = CFStringGetLength(pattern);
if(wholeString.length != 0)
CFStringDelete(pattern, wholeString);
GetRString(tempS,MESS_TITLE_PLUG);
CFStringAppendPascalString(pattern, tempS, CFStringGetSystemEncoding());
DoCFStringGlobalReplace(pattern, upTwo, temp);
temp = CFStringCreateWithPascalString(NULL, (*tocH)->sums[sumNum].from, ((*tocH)->sums[sumNum].flags & FLAG_UTF8) ? kCFStringEncodingUTF8 : CFStringGetSystemEncoding());
if(temp == NULL)
{
temp = CFStringCreateWithPascalString(NULL, (*tocH)->sums[sumNum].from, kCFStringEncodingMacRoman);
}
DoCFStringGlobalReplace(pattern, upOne, temp);
CFRelease(temp);
temp = CFStringCreateWithPascalString(NULL, (*tocH)->sums[sumNum].subj, ((*tocH)->sums[sumNum].flags & FLAG_UTF8) ? kCFStringEncodingUTF8 : CFStringGetSystemEncoding());
if(temp == NULL)
{
temp = CFStringCreateWithPascalString(NULL, (*tocH)->sums[sumNum].subj, kCFStringEncodingMacRoman);
}
DoCFStringGlobalReplace(pattern, upThree, temp);
CFRelease(temp);
GetMailboxName(tocH,sumNum,tempS);
temp = CFStringCreateWithPascalString(NULL, tempS, CFStringGetSystemEncoding());
DoCFStringGlobalReplace(pattern, upZero, temp);
CFRelease(temp);
return pattern;
}
/**********************************************************************
* PeteApplyStyles - apply a saved set of styles
**********************************************************************/
void PeteApplyStyles(PETEHandle pte,OffsetAndStyleHandle styles)
{
short n;
long endOffset;
OffsetAndStyle current;
if (!styles || !*styles) return;
n = HandleCount(styles);
if (!n) return;
endOffset = 0x7fffffff;
while (n--)
{
current = (*styles)[n];
PETESetTextStyle(PETE,pte,current.offset,endOffset,&current.style,current.validBits);
endOffset = current.offset;
}
PETEMarkDocDirty(PETE,pte,False);
}
/**********************************************************************
* GenerateReceipt - generate a receipt for a message, if need be
**********************************************************************/
OSErr GenerateReceipt(MessHandle messH,short forWhat,short forWhatLocal,short actionId,short sentId)
{
Accumulator a;
Str255 scratch;
Str63 date, subject, realname;
Str15 boundary;
OSErr err = userCancelled;
Str15 returnStr;
long len;
MyWindowPtr win;
PushPers(PERS_FORCE(MESS_TO_PERS(messH)));
// ok, the user wants to do it
PCopy(boundary,"\p--_");
PCopy(returnStr,Cr);
if (!(err=AccuInit(&a)))
{
// multipart/report
AccuComposeR(&a,MIME_MP_FMT,
InterestHeadStrn+hContentType,
MIME_MULTIPART,
MIME_REPORT,
AttributeStrn+aBoundary,
"\p_",
returnStr);
AccuAddStr(&a,GetRString(scratch,MDN_REPORT_PARAM));
AccuAddChar(&a,'\015');
// inital boundary
AccuAddStr(&a,boundary);
AccuAddChar(&a,'\015');
// text part
AccuComposeR(&a,MIME_TEXTPLAIN,InterestHeadStrn+hContentType,
MIME_TEXT,MIME_PLAIN,"",returnStr);
AccuAddChar(&a,'\015');
CompHeadGetStr(messH,DATE_HEAD,date);
CompHeadGetStr(messH,SUBJ_HEAD,subject);
GetRealname(realname);
AccuComposeR(&a,MDN_DESCRIP,date,subject,forWhatLocal,realname);
// next boundary
AccuAddStr(&a,boundary);
AccuAddChar(&a,'\015');
// disposition part
AccuComposeR(&a,MIME_TEXTPLAIN,InterestHeadStrn+hContentType,
MIME_MESSAGE,MDN_DISPO_NOTIFY,"",returnStr);
AccuAddChar(&a,'\015');
//AccuComposeR(&a,MDN_REPORTING_UA,returnStr);
AccuComposeR(&a,MDN_FINAL_RECIP,GetReturnAddr(scratch,False),returnStr);
if (!GetRHeaderAnywherePtr(messH,HeaderStrn+MSGID_HEAD,scratch+1,sizeof(scratch)-1,&len))
{
*scratch = len;
AccuComposeR(&a,MDN_ORIG_MID,scratch,returnStr);
}
AccuComposeR(&a,MDN_DISPOSITION,actionId,sentId,forWhat,returnStr);
AccuAddChar(&a,'\015');
AccuAddChar(&a,'\015');
//final boundary
AccuAddStr(&a,boundary);
AccuAddStr(&a,"\p--\015");
/*
* message built, now create & address it
*/
if (!GetRHeaderAnywherePtr(messH,InterestHeadStrn+hMDN,scratch,sizeof(scratch)-1,&len))
{
*scratch = len-1;
if (win=DoComposeNew(0))
{
MessHandle newMessH = Win2MessH(win);
SetMessText(newMessH,TO_HEAD,scratch+1,*scratch);
SetMessText(newMessH,0,LDRef(a.data),a.offset);
ComposeRString(scratch,MDN_SUBJECT,subject);
SetMessText(newMessH,SUBJ_HEAD,scratch+1,*scratch);
SetMessOpt(newMessH,OPT_RECEIPT);
SetMessOpt(newMessH,OPT_BULK);
SetSig((*newMessH)->tocH,(*newMessH)->sumNum,SIG_NONE);
QueueMessage((*newMessH)->tocH,(*newMessH)->sumNum,kEuQueue,0,true,false);
}
}
AccuZap(a);
}
PopPers();
return(err);
}
/**********************************************************************
* EnsureMID - make sure a message has an id
**********************************************************************/
OSErr EnsureMID(TOCHandle tocH,short sumNum)
{
OSErr err = noErr;
if ((*tocH)->sums[sumNum].uidHash==kNeverHashed || (*tocH)->sums[sumNum].msgIdHash==kNeverHashed)
{
if (!(err=CacheMessage(tocH,sumNum)))
RehashLo(tocH,sumNum,(*tocH)->sums[sumNum].cache,true);
}
return(err);
}
/**********************************************************************
* EnsureFromHash - make sure a message has a from id
**********************************************************************/
OSErr EnsureFromHash(TOCHandle tocH,short sumNum)
{
OSErr err = noErr;
Str255 scratch, shortAddr;
uLong addrHash;
if ((*tocH)->sums[sumNum].fromHash==kNeverHashed)
{
if (!(err=CacheMessage(tocH,sumNum)))
{
HNoPurge((*tocH)->sums[sumNum].cache);
HeaderName(FROM_HEAD); // weird--goes into scratch
TrimWhite(scratch);
if (*HandleHeadGetPStr((*tocH)->sums[sumNum].cache,HEADER_STRN+FROM_HEAD,scratch))
{
ShortAddr(shortAddr,scratch);
MyLowerStr(shortAddr);
addrHash = Hash(shortAddr);
}
else
addrHash = kNoMessageId;
HPurge((*tocH)->sums[sumNum].cache);
(*tocH)->sums[sumNum].fromHash = addrHash;
TOCSetDirty(tocH,true);
}
}
return(err);
}
/**********************************************************************
* CacheMessage - put a message into the cache
**********************************************************************/
OSErr CacheMessage(TOCHandle tocH, short sumNum)
{
Handle cache;
OSErr err = noErr;
if (0>sumNum || sumNum>=(*tocH)->count) return(fnfErr);
#ifdef IMAP
// don't do anything with IMAP messages that have not been downloaded yet.
if ((*tocH)->sums[sumNum].offset < 0) return (fnfErr);
#endif
/*
* is it there?
*/
if ((*tocH)->sums[sumNum].cache)
{
if (*(*tocH)->sums[sumNum].cache) return(noErr); /* in the cache */
else ZapHandle((*tocH)->sums[sumNum].cache); /* wipe out remnant */
}
/*
* allocate it
*/
if (cache=NuHTempOK(GetMessageLength(tocH,sumNum)))
{
/*
* read it
*/
err = ReadMessage(tocH,sumNum,LDRef(cache));
if (err) ZapHandle(cache);
else
{
UL(cache);
HPurge(cache);
(*tocH)->sums[sumNum].cache = cache;
ASSERT((*cache)[GetHandleSize(cache)-1]=='\015');
}
}
else
err = MemError();
return(err);
}
/**********************************************************************
* GetMessText - put the text of a message
**********************************************************************/
UHandle GetMessText(MessHandle messH)
{
MyWindowPtr win = (*messH)->win;
TOCHandle tocH = (*messH)->tocH;
int sumNum = (*messH)->sumNum;
UHandle buffer = nil;
long weeded;
short tableId;
Handle table;
Handle cache=nil;
Accumulator weeds;
Zero(weeds);
/*
* grab cached text, if any
*/
if ((*tocH)->sums[sumNum].cache && *(*tocH)->sums[sumNum].cache)
{
cache = (*tocH)->sums[sumNum].cache;
HNoPurge(cache);
}
else
ZapHandle((*tocH)->sums[sumNum].cache);
/*
* allocate buffer
*/
if (GetMessageLength(tocH,sumNum) > (GetRLong(PETE_NIBBLE) K))
buffer = NuHTempBetter(GetMessageLength(tocH,sumNum));
else
buffer = NuHTempOK(GetMessageLength(tocH,sumNum));
if (buffer==nil)
{
if (!cache)
{
WarnUser(NO_MESS_BUF,MemError());
return(nil);
}
}
/*
* read it
*/
if (cache)
{
if (buffer)
{
BMD(*cache,*buffer,GetHandleSize(buffer));
HPurge(cache);
}
else
{
buffer = cache;
cache = nil;
(*tocH)->sums[sumNum].cache = nil;
}
}
else
{
LDRef(buffer);
if (MessErr=ReadMessage(tocH,sumNum,*buffer))
goto failure;
UL(buffer);
if (cache = NuHTempOK(GetMessageLength(tocH,sumNum)))
{
HPurge(cache);
BMD(*buffer,*cache,GetMessageLength(tocH,sumNum));
(*tocH)->sums[sumNum].cache = cache;
}
}
/*
* set hash, if we haven't already
*/
if ((*tocH)->sums[sumNum].uidHash == kNeverHashed)
Rehash(tocH,sumNum,buffer);
/*
* weed headers?
*/
if (!(SumOf(messH)->flags & FLAG_SHOW_ALL))
{
WeedHeaders(buffer,&weeded,BadHeadStrn,&weeds);
(*messH)->weeded = weeded;
StripTrailingNewlines(buffer,SumOf(messH)->bodyOffset-weeded+1);
}
else
(*messH)->weeded = 0;
WeedHeaders(buffer,&weeded,FROM_STRN,nil);
(*messH)->weeded += weeded;
AccuTrim(&weeds);
(*messH)->extras = weeds;
/*
* translate?
*/
tableId = (*tocH)->sums[sumNum].tableId;
if (tableId==DEFAULT_TABLE) tableId = GetPrefLong(PREF_IN_XLATE);
if (tableId!=NO_TABLE)
{
if (!(table=GetResource_('taBL',tableId)))
{
tableId -= 1000; /* try 1000 lower */
table=GetResource_('taBL',tableId);
}
if (table)
{
Byte *p,*end,*t;
long size = GetHandleSize_(buffer);
end = *buffer+size;
t = *table;
for (p=*buffer;p<end;p++) *p = t[*p];
}
}
win->ro = !MessOptIsSet(messH,OPT_WRITE);
return(buffer);
failure:
if (buffer) ZapHandle(buffer);
return(nil);
}
/**********************************************************************
* ReadMessage - read a given message into a preallocated buffer
**********************************************************************/
int ReadMessage(TOCHandle tocH,int sumN,UPtr buffer)
{
long count;
Str63 name;
short sumNum;
tocH = GetRealTOC(tocH,sumN,&sumNum);
if (!tocH) return fnfErr; // unable to find real TOC from virtual TOC
GetMailboxName(tocH,sumNum,name);
count = (*tocH)->sums[sumNum].length;
if (!(MessErr=BoxFOpenLo(tocH,sumNum)))
if ((MessErr=SetFPos((*tocH)->refN,fsFromStart,
(*tocH)->sums[sumNum].offset)) ||
(MessErr=ARead((*tocH)->refN,&count,buffer)))
FileSystemError(READ_MBOX,name,MessErr);
return(MessErr);
}
/**********************************************************************
* MoveMessage - transfer a message from one box to another
* called when the transfer menu is invoked with a message frontmost
**********************************************************************/
int MoveMessage(TOCHandle tocH,int sumNum,FSSpecPtr toSpec,Boolean copy)
{
TOCHandle toTocH;
CycleBalls();
if ((toTocH = TOCBySpec(toSpec))==nil)
return(1);
if ((*toTocH)->which==OUT && !copy && !((*tocH)->sums[sumNum].flags&FLAG_OUT))
if (ReallyDoAnAlert(XFER_TO_OUT,Caution)!=1) return(1);
if (!copy)
{
if (SingleWarnings(tocH,sumNum,(*toTocH)->which==TRASH,False))
return(1);
}
return(MoveMessageLo(tocH,sumNum,toSpec,copy,false,true));
}
/**********************************************************************
* MoveMessageLo - transfer a message from one box to another, no warnings
**********************************************************************/
int MoveMessageLo(TOCHandle tocH,int sumNum,FSSpecPtr toSpec,Boolean copy,Boolean toTemp,Boolean holdOpen)
{
TOCHandle toTocH;
MessHandle messH = (*tocH)->sums[sumNum].messH;
Str32 name;
long serialNum;
short realSumNum;
Boolean isIMAPtoPopTransfer = false;
#ifdef THREADING_ON
// can't transfer a message we're sending
if (!copy && ((*tocH)->sums[sumNum].state == BUSY_SENDING))
return (-1);
#endif
if (LogLevel&LOG_MOVE)
ComposeLogS(LOG_MOVE,nil,"\p%p <20>%p,%p<> <20>%p<>-><3E>%p<>\015", copy ? "\pCopy" : "\pTransfer",(*tocH)->sums[sumNum].from,(*tocH)->sums[sumNum].subj,GetMailboxName(tocH,sumNum,name),toSpec->name);
CycleBalls();
if ((toTocH = TOCBySpec(toSpec))==nil)
return(1);
tocH = GetRealTOC(tocH,sumNum,&realSumNum);
sumNum = realSumNum;
#ifdef IMAP
// handle special transfer cases for IMAP mailbox transfers
if ((*toTocH)->imapTOC)
{
OSErr err = noErr;
// IMAP to IMAP. Do an IMAP transfer.
if ((*toTocH)->imapTOC && (*tocH)->imapTOC)
{
err = IMAPTransferMessage(tocH, toTocH, (*tocH)->sums[sumNum].uidHash, copy, false);
}
// POP to IMAP
else if ((*toTocH)->imapTOC && !(*tocH)->imapTOC)
{
err = IMAPTransferMessageToServer(tocH, toTocH, sumNum, copy, false);
}
return (err);
}
#endif
if ((*tocH)->which==OUT && !copy) FixSourceStatus(tocH,sumNum); /* in case we're deleting a reply, set orig state back */
if (messH && (*messH)->subPTE && PeteIsDirty((*messH)->subPTE))
MessSaveSub(messH);
#ifdef IMAP
// if this is an IMAP to POP transfer, close the message window
if ((*tocH)->imapTOC)
{
// IMAP to POP. Download the message.
Boolean downloaded = EnsureMsgDownloaded(tocH,sumNum,true);
// drop the message if a translator asked to delete it
if ((*tocH)->sums[sumNum].opts&OPT_EMSR_DELETE_REQUESTED)
{
// message was fetched, but a translator deleted it.
(*tocH)->sums[sumNum].opts |= OPT_ORPHAN_ATT; // be real sure the attachments are left alone
#ifdef DEBUG
ComposeLogS(LOG_PLUG,nil,"\pA plugin has deleted an IMAP message: '%p' in '%p'",(*tocH)->sums[sumNum].subj,(*tocH)->mailbox.spec.name);
#endif
return (noErr);
}
else if (!downloaded) return (1);
// if the message is open, close it.
if (!copy && messH)
CloseMyWindow(GetMyWindowWindowPtr((*messH)->win));
}
#endif
CycleBalls();
#ifdef IMAP
// if this is an IMAP to POP transfer, the POP copy will point to the attachment
isIMAPtoPopTransfer = tocH && (*tocH)->imapTOC && toTocH && !(*toTocH)->imapTOC;
#endif //IMAP
MessErr=AppendMessage(tocH,sumNum,toTocH,copy,toTemp,isIMAPtoPopTransfer);
if (MessErr) return(MessErr);
if (!holdOpen)
{
(void) BoxFClose(tocH,false);
(void) BoxFClose(toTocH,true);
}
CycleBalls();
#ifdef IMAP
// if we moved an IMAP message to a POP mailbox, forget about its attachments so they don't get tidied up.
if ((*tocH)->imapTOC && !(*toTocH)->imapTOC) (*tocH)->sums[sumNum].opts |= OPT_ORPHAN_ATT;
#endif
if (!copy)
#ifdef IMAP
{
serialNum = (*tocH)->sums[sumNum].serialNum;
// if this was an IMAP message, we'll need to delete it from the server.
if ((*tocH)->imapTOC)
{
MessErr = IMAPDeleteMessage(tocH, (*tocH)->sums[sumNum].uidHash, false, false, false) ? noErr : 1;
}
else DeleteSum(tocH,sumNum);
// Check for updates to search results
SearchUpdateSum(toTocH,(*toTocH)->count-1,tocH,serialNum,true,false);
}
#else
{
oldSerialNum = (*tocH)->sums[sumNum].serialNum;
DeleteSum(tocH,sumNum);
// Check for updates to search results
SearchUpdateSum(toTocH,(*toTocH)->count-1,fromTocH,serialNum,true,false);
}
#endif
CheckBox(GetWindowMyWindowPtr(FrontWindow_()),False);
return(MessErr);
}
/**********************************************************************
* AppendMessage - add a message to a mailbox. Message comes from another
* mailbox. Things in here are a little touchy, as there are several
* things to do, any one of which could fail. In order, this is
* what is done:
* 1. Move the bytes from one mailbox to the other.
* 2. Copy the message summary from one toc to the other, updating
* tocH and sumNum in the message handle (if any).
* 3. If the message window is open, fix pointers so the message
* belongs to the new box, not the old one.
* Steps 1 and 2 could fail. In either case, no real harm is done,
* except that we might waste some space in the new mailbox.
* Step 3 shouldn't ever fail.
**********************************************************************/
int AppendMessage(TOCHandle fromTocH,int fromN,TOCHandle toTocH,Boolean copy,Boolean toTemp,Boolean destHasAtt)
{
UHandle buffer = nil;
MSumType sum;
long eof;
long count;
short err=0;
MessHandle fromMH;
long newBodyOffset, newLength;
mesgErrorHandle mesgErrH;
FSSpec toSpec;
/*
* if it's an outgoing message, save it first
*/
fromMH = (*fromTocH)->sums[fromN].messH;
if (fromMH)
{
MyWindowPtr win = (*fromMH)->win;
if (win->saveSize && win->position) (*win->position)(True,win);
if ((*fromTocH)->which==OUT)
{
MyWindowPtr win = (*fromMH)->win;
if ((win->isDirty||!(*fromTocH)->sums[fromN].length) &&
!SaveComp(win)) return(1);
}
else if (win->isDirty)
{
MessFocus(fromMH,(*fromMH)->bodyPTE);
if (!SaveMessHi(win,False)) return(1);
}
}
/*
* open the relevant mailboxes
*/
MessErr = BoxFOpen(fromTocH);
if (MessErr) return(MessErr);
MessErr = BoxFOpen(toTocH);
if (MessErr) return(MessErr);
/*
* is there space?
*/
toSpec = GetMailboxSpec(toTocH,-1);
if (MessErr = VolumeMargin(toSpec.vRefNum,(*fromTocH)->sums[fromN].length))
return(MessErr);
/*
* Are there too many messages in the destination?
*/
if ((*toTocH)->count >= MAX_MESSAGES_PER_MAILBOX)
{
Str255 s;
ComposeStdAlert(Stop,TOO_MANY_MESSAGES,PCopy(s,(*toTocH)->mailbox.spec.name));
return MessErr = 1;
}
/*
* copy the message from one to the other
*/
if (!copy) (*fromTocH)->sums[fromN].flags &= ~FLAG_SKIPWARN;
eof = FindTOCSpot(toTocH,(*fromTocH)->sums[fromN].length);
if ((*toTocH)->which == OUT && !((*fromTocH)->sums[fromN].flags&FLAG_OUT))
{
if (MessErr = CopyToOut(fromTocH,fromN,toTocH)) return(MessErr);
}
else
{
#ifdef TWO
if (!copy && ((*fromTocH)->which==TRASH || (*toTocH)->which==TRASH))
{
if (PrefIsSet(PREF_TIDY_FOLDER) && ((*fromTocH)->sums[fromN].flags & FLAG_HAS_ATT) &&
!((*fromTocH)->sums[fromN].flags & FLAG_OUT))
MovingAttachments(fromTocH,fromN,True,False,False,false);
if ((*fromTocH)->sums[fromN].flags & FLAG_HAS_ATT)
MovingAttachments(fromTocH,fromN,False,False,False,false);
}
#endif
if ((*fromTocH)->sums[fromN].cache && *(*fromTocH)->sums[fromN].cache)
{
count = (*fromTocH)->sums[fromN].length;
HNoPurge((*fromTocH)->sums[fromN].cache);
MessErr = SetFPos((*toTocH)->refN,fsFromStart,eof);
if (!MessErr)
MessErr = AWrite((*toTocH)->refN,&count,LDRef((*fromTocH)->sums[fromN].cache));
#ifdef DEBUG
if (RunType!=Production)
{
long controls=0;
UPtr dbspot = *(*fromTocH)->sums[fromN].cache;
UPtr dbend = dbspot + (*fromTocH)->sums[fromN].length;
for (;dbspot<dbend;dbspot++)
if (*dbspot!='\015' && *dbspot<' ' && *dbspot!='\t') controls++;
if (controls*20>(*fromTocH)->sums[fromN].length)
{
Str255 buffer;
ComposeString(buffer,"\pWarning: %d control characters; may be a problem",controls);
AlertStr(OK_ALRT,Stop,buffer);
}
}
#endif
UL((*fromTocH)->sums[fromN].cache);
HPurge((*fromTocH)->sums[fromN].cache);
}
else
MessErr = CopyFBytes((*fromTocH)->refN,(*fromTocH)->sums[fromN].offset,
(*fromTocH)->sums[fromN].length,(*toTocH)->refN,eof);
if (MessErr)
{
LDRef(toTocH);
FileSystemError(COPY_FAILED,toSpec.name,MessErr);
UL(toTocH);
return(MessErr);
}
(void) SetEOF((*toTocH)->refN,eof+(*fromTocH)->sums[fromN].length);
newBodyOffset = (*fromTocH)->sums[fromN].bodyOffset;
newLength = (*fromTocH)->sums[fromN].length;
/*
* now, create a new summary for the copied message, and put it in the
* new TOC.
*/
sum = (*fromTocH)->sums[fromN];
sum.offset = eof;
sum.length = newLength;
sum.bodyOffset = newBodyOffset;
sum.selected = False;
sum.messH = nil; /* break connection with open message window */
sum.mesgErrH = nil; /* clear mesgErrH. add it below */
sum.serialNum = (*toTocH)->nextSerialNum++;
// Junk processing
if ((*toTocH)->which==JUNK && sum.spamBecause==0)
{
sum.spamScore = GetRLong(JUNK_XFER_SCORE);
sum.spamBecause = JUNK_BECAUSE_XFER;
}
else if ((*fromTocH)->which==JUNK && (*fromTocH)->sums[fromN].spamBecause == JUNK_BECAUSE_XFER)
{
sum.spamScore = 0;
sum.spamBecause = 0;
}
if (copy)
sum.cache = nil; // because we're copying, leave the cache alone
else
(*fromTocH)->sums[fromN].cache = nil; // let the cache go with the transferred message
// the copy won't have an attachment. That way, if it gets deleted, the attachment
// will remain until the original is deleted. - jdboyd12/14/04
if (copy && !destHasAtt)
sum.flags &= ~FLAG_HAS_ATT;
if (!sum.seconds) sum.seconds = GMTDateTime();
if ((*fromTocH)->which==OUT && !toTemp)
{
if (sum.state!=SENT) sum.state=UNSENT;
sum.tableId = NO_TABLE; /* don't translate */
sum.flags |= FLAG_OUT;
}
if ((*toTocH)->count)
{
MessErr = PtrPlusHand_(&sum,toTocH,sizeof(sum));
if (MessErr)
{
ZapHandle(sum.cache);
return(MessErr);
}
}
else
(*toTocH)->sums[0] = sum;
TOCSetDirty(toTocH,true);
(*toTocH)->reallyDirty = True;
(*toTocH)->resort = kResortWhenever;
(*toTocH)->count++;
(*toTocH)->analScanned = false;
}
TOCSetDirty(toTocH,true);
(*toTocH)->needRedo = MIN((*toTocH)->needRedo,(*toTocH)->count-1);
/*
* add mesg error to new toc entry
*/
if (mesgErrH=(*fromTocH)->sums[fromN].mesgErrH)
{
Str255 errorStr;
AddMesgError (toTocH, (*toTocH)->count-1,PCopy(errorStr,(*mesgErrH)->errorStr), (*mesgErrH)->errorCode);
}
/*
* the message window, if any, should be closed
*/
if (!copy && (*fromTocH)->sums[fromN].messH)
CloseMyWindow(GetMyWindowWindowPtr((*(MessHandle)(*fromTocH)->sums[fromN].messH)->win));
return (MessErr); //return(noErr); // 12/11/97 ccw
}
/**********************************************************************
* MoveSelectedMessages - transfer all selected messages from one mail
* box to another.
**********************************************************************/
int MoveSelectedMessages(TOCHandle tocH,FSSpecPtr toSpec,Boolean copy)
{
return (MoveSelectedMessagesLo(tocH, toSpec, copy, false, true, true));
}
/**********************************************************************
* MoveSelectedMessages - transfer all selected messages from one mail
* box to another.
**********************************************************************/
int MoveSelectedMessagesLo(TOCHandle tocH,FSSpecPtr toSpec,Boolean copy,Boolean delete,Boolean undo,Boolean warnings)
{
TOCHandle toTocH;
int sumNum;
int lastSelected = -1;
Str31 trashName;
short oldCount;
Boolean toTrash = IsRoot(toSpec) && StringSame(toSpec->name,GetRString(trashName,TRASH));
long needRoom=0;
Boolean outWarning;
long count;
uLong pTicks = TickCount();
#ifdef IMAP
Handle uidsH = nil;
OSErr err = noErr;
#endif
TOCHandle realTocH;
short realSum;
Str255 name;
if ((toTocH = TOCBySpec(toSpec))==nil)
return(1);
outWarning = !copy && (*toTocH)->which==OUT;
//
// Do transfers from the search window. Fall through to handle POP messages.
//
if ((*tocH)->virtualTOC)
{
if (delete) IMAPDeleteMessagesFromSearchWindow(tocH);
else
{
// do search window transfers
err = IMAPTransferMessagesFromSearchWindow(tocH, toTocH, copy);
if (err != noErr) return (err);
}
}
//
// Perform transfers to IMAP mailboxes. There will be nothing left to do after this.
//
if ((*toTocH)->imapTOC)
{
return (IMAPMoveIMAPMessages(tocH, toTocH, copy));
}
//
// Handle the rest of the messages that have not been transferred yet.
//
if (!copy && warnings && SelectedWarnings(tocH,toTrash,False)) return(0);
count = CountSelectedMessages(tocH);
for (sumNum=0;sumNum<(*tocH)->count;sumNum++)
if ((*tocH)->sums[sumNum].selected)
{
FixSourceStatus(tocH,sumNum); /* in case we're deleting a reply, set orig state back */
needRoom += (*tocH)->sums[sumNum].length+sizeof(MSumType);
if (outWarning && !((*tocH)->sums[sumNum].flags&FLAG_OUT))
{
outWarning = False; /* don't need anymore */
if (ReallyDoAnAlert(XFER_TO_OUT,Caution)!=1) return(0);
else break;
}
}
NoteFreeSpace(toTocH);
if (needRoom>(*toTocH)->volumeFree-GetRLong(VOLUME_MARGIN)-sizeof(MSumType)*count)
{
WarnUser(NOT_ENOUGH_ROOM,dskFulErr);
return(0);
}
#ifdef TWO
if (!copy && undo) AddXfUndo(tocH,toTocH,-1);
#endif
if (count>10) OpenProgress();
ProgressMessageR(kpSubTitle,LEFT_TO_TRANSFER);
Progress(NoChange,count,nil,nil,nil);
//
// If we are transferring from an IMAP mailbox to a POP mailbox,
// ensure that the messages have been fetched.
//
if ((*tocH)->imapTOC)
{
short c, totalToDownload;
// must be online to do an IMAP to POP transfer, no matter if the message is downloaded or not.
if (!copy && Offline && GoOnline()) return (0);
// go through selected messages, and make sure they've all been downloaded.
if ((totalToDownload = CountSelectedMessages(tocH))>0)
{
// figure out how many of the selected messages need to be downloaded
for (sumNum=0;sumNum<(*tocH)->count;sumNum++)
if ((*tocH)->sums[sumNum].selected)
if (IMAPMessageBeingDownloaded(tocH, sumNum) || IMAPMessageDownloaded(tocH, sumNum)) totalToDownload--;
// Make a handle big enough for them
if (totalToDownload > 0)
{
uidsH = NuHandleClear(totalToDownload*sizeof(unsigned long));
if (uidsH)
{
// and stick them in the handle
c = totalToDownload;
for (sumNum=0;sumNum<(*tocH)->count;sumNum++)
if ((*tocH)->sums[sumNum].selected)
if (!IMAPMessageBeingDownloaded(tocH, sumNum) && !IMAPMessageDownloaded(tocH, sumNum))
BMD(&((*tocH)->sums[sumNum].uidHash),&((unsigned long *)(*uidsH))[--c],sizeof(unsigned long));
// fetch them all in the foreground
err = UIDDownloadMessages(tocH, uidsH, true, false);
if (err == noErr)
{
short i;
// put ALL of the messages in the mailbox.
for (i=0; i < totalToDownload; i++) UpdateIMAPMailbox(tocH);
}
}
else
{
err = MemError();
}
}
}
if (err==noErr)
{
// make a new handle to store uids of deleted messages. We'll build this on the fly.
uidsH = NuHandleClear(0);
if (!uidsH) err = MemError();
}
if (err != noErr)
{
if (!CommandPeriod && (err!=OFFLINE)) WarnUser(err, MEM_ERR);
return (err);
}
}
for (sumNum = 0; sumNum < (*tocH)->count; sumNum++)
{
if ((*tocH)->sums[sumNum].selected)
{
CycleBalls();
if (count>1) MiniEvents();
if (EjectBuckaroo || CommandPeriod) break;
if (!(--count%10) || TickCount()-pTicks>30)
{
Progress(NoBar,count,nil,nil,nil);
pTicks = TickCount();
}
#ifdef IMAP
// if this is a virtual TOC, and the message has already been processed, skip it.
if ((*tocH)->virtualTOC && ((*tocH)->sums[sumNum].u.virtualMess.virtualMBIdx == -1)) continue;
if ((*tocH)->imapTOC && !(*tocH)->virtualTOC)
{
// Make sure IMAP message has been downloaded and stuck in the mailbox
if (!EnsureMsgDownloaded(tocH,sumNum,true))
continue; // Couldn't download message
}
#endif
oldCount = (*tocH)->count;
if (!(realTocH = GetRealTOC(tocH,sumNum,&realSum))) continue;
#ifdef IMAP
// if this is a virtual mailbox, and the real mailbox is an IMAP mailbox, don't do anything with this message
if ((*tocH)->virtualTOC && (*realTocH)->imapTOC) MessErr = noErr;
else
#endif
MessErr=AppendMessage(realTocH,realSum,toTocH,copy,false,false);
if (MessErr) {if (MessErr!=userCanceledErr) WarnUser(WRITE_MBOX,MessErr); break;}
#ifdef IMAP
// if we moved this IMAP message to a POP mailbox, forget about its attachments so they don't get tidied up.
if ((*tocH)->imapTOC && !(*toTocH)->imapTOC) (*tocH)->sums[sumNum].opts |= OPT_ORPHAN_ATT;
#endif
// Log the transfer
if (LogLevel&LOG_MOVE)
ComposeLogS(LOG_MOVE,nil,"\p%p <20>%p,%p<> <20>%p<>-><3E>%p<>\015", copy ? "\pCopy" : "\pTransfer",(*realTocH)->sums[realSum].from,(*realTocH)->sums[realSum].subj,GetMailboxName(realTocH,realSum,name),toSpec->name);
if (oldCount!=(*tocH)->count)
{
lastSelected = sumNum;
sumNum--;
}
else if (!copy)
{
long serialNum = (*realTocH)->sums[realSum].serialNum;
if (!(*tocH)->virtualTOC)
{
#ifdef IMAP
if ((*tocH)->imapTOC && uidsH)
{
// If not copying, we'll need to delete original IMAP message when done.
if (uidsH && !copy)
{
unsigned long uid = (*tocH)->sums[sumNum].uidHash;
err = PtrAndHand(&uid,uidsH,sizeof(uid));
if (err != noErr)
{
WarnUser(err,MEM_ERR);
ZapHandle(uidsH);
return(err);
}
}
}
#endif
else if (!DeleteSum(tocH,sumNum))
{
lastSelected = sumNum;
sumNum--; /* back up, so we can try again */
}
else
lastSelected = sumNum;
}
if (realTocH != tocH)
// delete real message summary
DeleteSum(realTocH,realSum);
// Check for updates to search results
SearchUpdateSum(toTocH,(*toTocH)->count-1,realTocH,serialNum,true,false);
}
}
}
#ifdef IMAP
// now delete the IMAP messages that were transferred, if the transfer completed successfully.
if (!copy && (*tocH)->imapTOC && (MessErr==noErr) && !CommandPeriod && !EjectBuckaroo)
{
// uidsH contains uids of messages that have been successfully transferred.
IMAPDeleteMessages(tocH,uidsH,false,false,false,false);
}
#endif
(void) BoxFClose(tocH,false);
(void) BoxFClose(toTocH,true);
CloseProgress();
if ((*tocH)->win && !copy && !CommandPeriod) BoxSelectAfter((*tocH)->win,lastSelected);
CheckBox(GetWindowMyWindowPtr(FrontWindow_()),False);
#ifdef TWO
if (MessErr) NukeXfUndo();
#endif
ShowBoxSizes((*tocH)->win);
return(MessErr);
}
/**********************************************************************
* SelectedWarnings - Warnings for the selected message
**********************************************************************/
OSErr SelectedWarnings(TOCHandle tocH,Boolean toTrash,Boolean nuke)
{
Boolean queuedWarning, unsentWarning, unreadWarning, busyWarning;
OSErr err = noErr;
short i;
MessageWarnings(tocH,-1,toTrash,nuke,&queuedWarning,&unsentWarning,&unreadWarning, &busyWarning);
for (i=0;i<(*tocH)->count && !err;i++)
if ((*tocH)->sums[i].selected)
err = MessageWarnings(tocH,i,toTrash,nuke,
&queuedWarning,&unsentWarning,&unreadWarning,&busyWarning);
return(err);
}
/**********************************************************************
* SingleWarnings - Give warnings for a single message
**********************************************************************/
OSErr SingleWarnings(TOCHandle tocH,short sumNum,Boolean toTrash,Boolean nuke)
{
Boolean queuedWarning, unsentWarning, unreadWarning, busyWarning;
MessageWarnings(tocH,-1,toTrash,nuke,&queuedWarning,&unsentWarning,&unreadWarning,&busyWarning);
return(MessageWarnings(tocH,sumNum,toTrash,nuke,
&queuedWarning,&unsentWarning,&unreadWarning,&busyWarning));
}
/**********************************************************************
*
**********************************************************************/
OSErr MessageWarnings(TOCHandle tocH, short sumNum,Boolean toTrash,Boolean nuke,
Boolean *queuedWarning,Boolean *unsentWarning,Boolean *unreadWarning,Boolean *busyWarning)
{
if (sumNum<0)
{
*queuedWarning = (*tocH)->which==OUT && !PrefIsSet(PREF_EASY_DEL_QUEUED);
*unreadWarning = !PrefIsSet(PREF_EASY_DEL_UNREAD);
*unsentWarning = (*tocH)->which==OUT && !PrefIsSet(PREF_EASY_DEL_UNSENT);
*busyWarning = true; // for now, until/if we get a pref for it
}
else
{
short button, verb;
if (toTrash)
if (nuke)
{
button = NUKE_BTN;
verb = NUKE_VERB;
}
else
{
button = TRASH_BTN;
verb = TRASH_VERB;
}
else
{
button = XFER_BTN;
verb = XFER_VERB;
}
// clarence 4/25/97
if ((*tocH)->sums[sumNum].state == BUSY_SENDING) {
WarnUser (SENDING_WARNING, 0);
*busyWarning = False;
return(1);
}
if ((*tocH)->sums[sumNum].flags&FLAG_SKIPWARN) return(noErr);
if (toTrash && *unreadWarning && ((*tocH)->sums[sumNum].state==UNREAD))
{
if (!Mom(button,0,PREF_EASY_DEL_UNREAD,UNREAD_WARNING,verb)) return(1);
*unreadWarning = False; /* been there, done that. */
}
if (*queuedWarning && IsQueued(tocH,sumNum))
{
if (!Mom(button,0,PREF_EASY_DEL_QUEUED,QUEUED_WARNING,verb)) return(1);
*queuedWarning = *unsentWarning = False; /* been there, done that. */
}
if (*unsentWarning && ((*tocH)->sums[sumNum].state!=SENT))
{
if (!Mom(button,0,PREF_EASY_DEL_UNSENT,UNSENT_WARNING,verb)) return(1);
*unsentWarning = False; /* been there, done that. */
}
}
return(noErr);
}
/**********************************************************************
* FixSourceStatus - fix the status of a source message
**********************************************************************/
void FixSourceStatus(TOCHandle tocH,short sumNum)
{
MessHandle messH = (*tocH)->sums[sumNum].messH;
TOCHandle sourceTocH;
short sourceNum;
uLong **midList;
short i;
if ((*tocH)->which == OUT && messH && SumOf(messH)->state != SENT &&(*messH)->aSourceMID.offset)
{
midList = (*messH)->aSourceMID.data;
for (i=(*messH)->aSourceMID.offset/(3*sizeof(long))-1;i>=0;i--)
{
uLong sourceMID = (*midList)[i*3];
short sourceOrigState = (*midList)[i*3+1];
short sourceNewState = (*midList)[i*3+2];
if (sourceMID && sourceMID != kNeverHashed && sourceOrigState != sourceNewState)
{
if (!FindMessageByMID(sourceMID,&sourceTocH,&sourceNum))
{
if (sourceNewState == (*sourceTocH)->sums[sourceNum].state)
SetState(sourceTocH,sourceNum,sourceOrigState);
}
}
}
AccuZap((*messH)->aSourceMID);
}
}
/**********************************************************************
* FindMessageByMID - find a message by mid. Works only on open toc's at the moment
**********************************************************************/
OSErr FindMessageByMID(uLong mid,TOCHandle *tocH,short *sumNum)
{
TOCHandle lTocH;
long lsum;
for (lTocH=TOCList;lTocH;lTocH = (*lTocH)->next)
{
if (!TOCFindMessByMID(mid,lTocH,&lsum))
{
*sumNum = lsum;
*tocH = lTocH;
return(noErr);
}
}
return(fnfErr);
}
/**********************************************************************
* TOCFindMessByMID - find a message in a toc by uid hash
**********************************************************************/
OSErr TOCFindMessByMID(uLong mid,TOCHandle tocH,long *sumNum)
{
short lSumNum;
for (lSumNum=(*tocH)->count-1;lSumNum>=0;lSumNum--)
if ((*tocH)->sums[lSumNum].uidHash == mid)
{
*sumNum = lSumNum;
return(noErr);
}
return(fnfErr);
}
/**********************************************************************
* TOCFindMessByMsgID - find a message in a toc by message id
**********************************************************************/
OSErr TOCFindMessByMsgID(uLong mid,TOCHandle tocH,long *sumNum)
{
short lSumNum;
for (lSumNum=(*tocH)->count-1;lSumNum>=0;lSumNum--)
if ((*tocH)->sums[lSumNum].msgIdHash == mid)
{
*sumNum = lSumNum;
return(noErr);
}
return(fnfErr);
}
#ifdef TWO
/************************************************************************
* MovingAttachments - we're moving attachments into or out of the trash
************************************************************************/
void MovingAttachments(TOCHandle tocH,short sumNum,Boolean attach,Boolean wipe,Boolean nuke,Boolean IMAPStubsOnly)
{
FSSpec attFolder, trashFolder;
FSSpecPtr fromSpec, toSpec;
if (attach)
{
if (GetAttFolderSpec(&attFolder)) return; /* no folder set */
}
else
SubFolderSpec(PARTS_FOLDER,&attFolder);
if (GetTrashSpec(attFolder.vRefNum,&trashFolder)) return; /* oh well; unimportant error */
if (!nuke && (*tocH)->which==TRASH)
{
fromSpec = &trashFolder;
toSpec = &attFolder;
}
else
{
fromSpec = &attFolder;
toSpec = &trashFolder;
}
MovingAttachmentsLo(tocH,sumNum,attach,wipe,nuke,IMAPStubsOnly,fromSpec,toSpec);
}
/************************************************************************
* MovingAttachmentsLo - we're moving attachments to specific folders
************************************************************************/
void MovingAttachmentsLo(TOCHandle tocH,short sumNum,Boolean attach,Boolean wipe,Boolean nuke,Boolean IMAPStubsOnly,FSSpecPtr fromSpec,FSSpecPtr toSpec)
{
Handle text;
long offset;
FSSpec attSpec, dupSpec, newSpec;
Boolean iOpened = (*tocH)->sums[sumNum].messH == nil;
TOCHandle attTOCH;
FInfo info;
CacheMessage(tocH,sumNum);
if (!(text=(*tocH)->sums[sumNum].cache)) return;
HNoPurge(text);
offset = (*tocH)->sums[sumNum].bodyOffset-1;
while (0<=(offset = FindAnAttachment(text,offset+1,&attSpec,attach,nil,nil,nil)))
{
#ifdef IMAP
if (IsIMAPAttachmentStub(&attSpec))
{
// Just delete IMAP attachment stubs
if (wipe || nuke) FSpDelete(&attSpec);
continue;
}
if (IMAPStubsOnly)
continue;
#endif
if (SameVRef(attSpec.vRefNum,toSpec->vRefNum) &&
(!fromSpec || AttStillInFolder(&attSpec,fromSpec)))
{
/*
* is it a mailbox?
*/
FSpGetFInfo(&attSpec,&info);
if (!(info.fdFlags&kIsAlias) && IsMailbox(&attSpec))
{
dupSpec = attSpec;
/*
* if it's open, we must close it
*/
if (attTOCH = FindTOC(&attSpec))
{
Boolean oldSuper = PrefIsSet(PREF_SUPERCLOSE);
if (!oldSuper) TogglePref(PREF_SUPERCLOSE);
CloseMyWindow(GetMyWindowWindowPtr((*attTOCH)->win));
if (!oldSuper) TogglePref(PREF_SUPERCLOSE);
}
Box2TOCSpec(&attSpec,&dupSpec);
if (wipe) WipeSpec(&dupSpec);
else SpecMove(&dupSpec,toSpec); /* if toc move fails, at least we tried */
/*
* now, if in the menus, kill the menu item
*/
if (!FSMakeFSSpec(MailRoot.vRef,MailRoot.dirId,attSpec.name,&dupSpec))
{
if (IsAlias(&dupSpec,&dupSpec) && SameSpec(&attSpec,&dupSpec))
{
FSMakeFSSpec(MailRoot.vRef,MailRoot.dirId,attSpec.name,&dupSpec);
RemoveBoxHigh(&dupSpec);
FSpDelete(&dupSpec); /* get rid of alias */
Box2TOCSpec(&dupSpec,&dupSpec);
FSpDelete(&dupSpec); /* and toc alias */
}
}
}
/*
* move the file into the trash
*/
if (wipe) WipeSpec(&attSpec);
else if (dupFNErr==SpecMove(&attSpec,toSpec))
{
/* dup filename. rename. */
dupSpec = *toSpec;
PCopy(dupSpec.name,attSpec.name);
newSpec = dupSpec;
UniqueSpec(&newSpec,31);
if (!FSpRename(&dupSpec,newSpec.name))
SpecMove(&attSpec,toSpec);
}
}
}
}
/************************************************************************
* AttStillInFolder - is an attachment still in the attachments folder?
************************************************************************/
Boolean AttStillInFolder(FSSpecPtr att, FSSpecPtr folder)
{
if (PrefIsSet(PREF_ATT_SUBFOLDER_TRASH))
return(SpecInSubfolderOf(att,folder));
else
return(SameVRef(att->vRefNum,folder->vRefNum) && att->parID==folder->parID);
}
#endif
/************************************************************************
* FindAnAttachment - find an attachment from a line of text
************************************************************************/
long FindAnAttachment(Handle text,long offset,FSSpecPtr spec,Boolean attach,uLong *cid, uLong *relURL, uLong *absURL)
{
UPtr spot,newLine,end;
Boolean result=False;
Str255 line;
LDRef(text);
end = *text + GetHandleSize_(text);
spot=*text+offset;
while ( spot<end && *spot++!='\015');
for (;spot<end;spot=newLine+1)
{
for (newLine=spot;newLine<end && *newLine!='\015';newLine++);
if (newLine-spot>24 && newLine-spot<255)
{
MakePStr(line,spot,newLine-spot);
if (attach)
{
result = !AttLine2Spec(line,spec,False);
if (result) break;
}
else
{
result = !RelLine2Spec(line,spec,cid,relURL,absURL);
if (result) break;
}
}
}
offset = result ? spot-*text : -1;
UL(text);
return(offset);
}
/**********************************************************************
* InitAttachmentFinder - initialize data for finding attachments in
* outgoing or received messages
**********************************************************************/
void InitAttachmentFinder(FindAttPtr pData,Handle text,Boolean attach,TOCHandle tocH,MSumPtr sum)
{
pData->text = text;
pData->outgoing = (*tocH)->which==OUT || sum->state==SENT || sum->state==UNSENT || sum->flags & FLAG_OUT;
pData->attach = attach;
if (pData->outgoing)
{
// Outgoing message. Find Attachments header
Str63 hdrName;
GetRString(hdrName,HeaderStrn+ATTACH_HEAD);
HandleHeadFindStr(text,hdrName,&pData->hs);
}
else
{
// Received message. Start at beginning of message body
pData->offset = sum->bodyOffset-1;
}
}
/**********************************************************************
* GetNextAttachment - find next attachment
**********************************************************************/
Boolean GetNextAttachment(FindAttPtr pData,FSSpecPtr spec)
{
Boolean result = false;
if (pData->text)
{
if (pData->outgoing)
{
result = pData->attach ? (GetIndAttachmentLo(pData->text,1,spec,nil,&pData->hs)!=1) : false;
}
else
{
pData->offset = FindAnAttachment(pData->text,pData->offset,spec,pData->attach,nil,nil,nil);
result = pData->offset != -1;
}
if (!result)
// No more attachments. Signal we are done
pData->text = nil;
}
return result;
}
/**********************************************************************
* DeleteMessage - delete a summary from a toc, and fix the screen, too
**********************************************************************/
void DeleteMessage(TOCHandle tocH, int sumNum,Boolean nuke)
{
#ifdef IMAP
if ((*tocH)->imapTOC)
{
// close the IMAP message to be deleted, even if it's just going to be marked.
MessHandle messH = (MessHandle)(*tocH)->sums[sumNum].messH;
if (messH) CloseMyWindow(GetMyWindowWindowPtr((*messH)->win));
IMAPDeleteMessage(tocH, (*tocH)->sums[sumNum].uidHash, nuke, false, false);
return;
}
#endif
if (SingleWarnings(tocH,sumNum,True,nuke||(*tocH)->which==TRASH)) return;
DeleteMessageLo(tocH,sumNum,nuke);
}
/**********************************************************************
* DeleteMessage - delete a summary from a toc, and fix the screen, too
**********************************************************************/
void DeleteMessageLo(TOCHandle tocH, int sumNum,Boolean nuke)
{
MessHandle messH = (MessHandle)(*tocH)->sums[sumNum].messH;
Boolean dirt = 0;
FSSpec trashSpec;
int oldN = (*tocH)->count;
Boolean wipe = PrefIsSet(PREF_WIPE)&&((*tocH)->sums[sumNum].opts&OPT_WIPE);
if ((*tocH)->which!=TRASH && !wipe && !nuke)
{
trashSpec.vRefNum = MailRoot.vRef;
trashSpec.parID = MailRoot.dirId;
GetRString(trashSpec.name,TRASH);
MoveMessageLo(tocH,sumNum,&trashSpec,False,False,true);
}
else
{
#ifdef TWO
if (!(*tocH)->imapTOC) NukeXfUndo();
#endif
if (wipe) WipeMessage(tocH,sumNum);
#ifdef TWO
if ((!(*tocH)->imapTOC) && PrefIsSet(PREF_SERVER_DEL))
{
AddTSToPOPD(DELETE_ID,tocH,sumNum,False);
}
if ((*tocH)->sums[sumNum].opts & OPT_HAS_SPOOL) RemSpoolFolder((*tocH)->sums[sumNum].uidHash);
if (nuke &&((*tocH)->sums[sumNum].flags&FLAG_HAS_ATT))
{
#ifdef IMAP
// move the attachment to the trash if the pref is set, as long as they haven't been orphaned.
if (PrefIsSet(PREF_TIDY_FOLDER) && !((*tocH)->sums[sumNum].opts & OPT_ORPHAN_ATT))
MovingAttachments(tocH,sumNum,True,wipe,True,false);
else
MovingAttachments(tocH,sumNum,True,wipe,True,true);
#else
if (PrefIsSet(PREF_TIDY_FOLDER)) MovingAttachments(tocH,sumNum,True,wipe,True,false);
#endif
#ifdef IMAP
// move the inline parts to the trash, unless this is an IMAP message that has been copied to a local mailbox
if (!((*tocH)->sums[sumNum].opts & OPT_ORPHAN_ATT))
#endif
MovingAttachments(tocH,sumNum,False,wipe,True,false);
}
#endif
if (messH) CloseMyWindow(GetMyWindowWindowPtr((*messH)->win));
if ((*tocH)->count==oldN) DeleteSum(tocH,sumNum);
}
}
/**********************************************************************
* WipeMessage - clear the contents of a message, really
**********************************************************************/
OSErr WipeMessage(TOCHandle tocH,short sumNum)
{
OSErr err;
MovingAttachments(tocH,sumNum,True,True,False,false);
MovingAttachments(tocH,sumNum,False,True,False,false);
if (err=BoxFOpen(tocH)) return(err);
err = WipeDiskArea((*tocH)->refN,(*tocH)->sums[sumNum].offset,(*tocH)->sums[sumNum].length);
return err;
}
/************************************************************************
* MessageError - return the most recent error code from these functions
************************************************************************/
int MessageError(void)
{
return(MessErr);
}
/**********************************************************************
* StripTrailingNewlines - strip the newlines off the end of a text block
**********************************************************************/
long StripTrailingNewlines(Handle buffer,long stop)
{
long size = GetHandleSize(buffer);
Ptr spot = *buffer + size;
long newSize;
while (spot[-1]=='\015' && spot>*buffer+stop) spot--;
newSize = spot - *buffer;
SetHandleSize(buffer,newSize);
return(size-newSize);
}
/************************************************************************
* WeedHeaders - weed a message's headers, leaving only the interesting
* ones.
************************************************************************/
void WeedHeaders(UHandle buffer,long *weeded,short toWeed,AccuPtr weeds)
{
long size;
short bad;
Handle badH = GetResource('STR#',toWeed);
UPtr badSpot; //pointer to bad header string we're currently looking for
Byte old;
short res;
if (badH)
{
UPtr spot; // char we're examining
UPtr done; // just past end of good chars that have been copied
UPtr end; // end of the whole shebang
UPtr found; // beginning of text we found
short badN;
HNoPurge(badH);
PtrAndHand("",badH,1); /* room for a null terminator */
LDRef(badH);
done = spot = LDRef(buffer);
badN = CountStrn(toWeed);
end = spot + GetHandleSize_(buffer);
// while there are chars left
while (spot<end)
{
if (*spot=='\015') break; //two returns in a row are the end of the headers
// search for the header name
badSpot = *badH+2;
for (bad=0;bad<badN;bad++)
{
old = badSpot[*badSpot+1];
badSpot[*badSpot+1] = 0;
res = striscmp(spot,badSpot+1);
badSpot[*badSpot+1] = old;
// found it!
if (!res)
{
// skip the header
found = spot;
while (spot<end)
if (*spot++=='\015')
if (*spot!=' ' && *spot!='\t') break;
// copy?
if (weeds) AccuAddPtr(weeds,found,spot-found);
goto nextHead; // continue looking at next header
}
else badSpot += *badSpot+1;
}
// copy the current header
while (spot<end)
if ((*done++ = *spot++)=='\015')
if (*spot!=' ' && *spot!='\t') break;
nextHead:;
}
while (spot<end) *done++ = *spot++;
size = done - *buffer;
if (weeded) *weeded = end-done;
UL(badH);
HPurge(badH);
HUnlock(buffer);
SetHandleBig_(buffer,size);
}
}
/************************************************************************
* SetMessText - stick some text into one of the fields of a message.
************************************************************************/
OSErr SetMessText(MessHandle messH,short whichTXE,UPtr string,long size)
{
HeadSpec hs;
if (CompHeadFind(messH,whichTXE,&hs))
return(CompHeadSetPtr(TheBody,&hs,string,size));
else return(fnfErr);
}
/**********************************************************************
* Fix1MessServerArea - fix the server display of a single message
**********************************************************************/
void Fix1MessServerArea(MyWindowPtr win)
{
WindowPtr winWP = GetMyWindowWindowPtr(win);
uLong uidHash;
MessHandle messH;
ControlHandle fetch;
ControlHandle trash;
Boolean onFetch, onDelete, lmos;
OSType popdType;
PushGWorld();
if (GetWindowKind(winWP)==MESS_WIN && IsWindowVisible(winWP))
{
messH = Win2MessH(win);
uidHash = SumOf(messH)->uidHash;
popdType = PERS_POPD_TYPE(MESS_TO_PPERS(messH));
fetch = FindControlByRefCon(win,mcFetch);
trash = FindControlByRefCon(win,mcTrash);
if (IdIsOnPOPD(popdType,POPD_ID,uidHash))
{
lmos = PrefIsSet(PREF_LMOS);
onFetch = IdIsOnPOPD(popdType,FETCH_ID,uidHash);
onDelete = IdIsOnPOPD(popdType,DELETE_ID,uidHash);
if ((*messH)->hasFetchIcon = MessFlagIsSet(messH,FLAG_SKIPPED))
if (fetch) {SetControlValue(fetch,onFetch); ShowControl(fetch);}
(*messH)->hasDelIcon = True;
if (trash)
{
SetControlValue(trash,onDelete);
HiliteControl(trash,onFetch && !lmos ? 255 : 0);
ShowControl(trash);
}
}
else
{
(*messH)->hasFetchIcon = (*messH)->hasDelIcon = False;
if (fetch) HideControl(fetch);
if (trash) HideControl(trash);
}
}
else if (GetWindowKind(winWP)==MBOX_WIN || GetWindowKind(winWP)==CBOX_WIN)
{
InvalTocBox((TOCHandle)GetMyWindowPrivateData(win),-2,blServer);
}
PopGWorld();
}
/**********************************************************************
* RecordTransAttachments - record the fact that we have attachments from a translator
**********************************************************************/
OSErr RecordTransAttachments(FSSpecPtr spec)
{
WindowPtr InsertWinWP = GetMyWindowWindowPtr (InsertWin);
MessHandle messH;
FSSpecHandle h;
if (InsertWin && GetWindowKind(InsertWinWP)==MESS_WIN)
{
messH = Win2MessH(InsertWin);
if (!(*messH)->etlFiles)
{
h = NuHTempBetter(0);
if (!h) return(MemError());
(*messH)->etlFiles = h;
}
#ifdef NEVER
if (RunType!=Production)
{
Str255 title;
MyGetWTitle(InsertWinWP,title);
Dprintf("\p;log RecordTransAttachments;sc;g");
Dprintf("\p<EFBFBD>%p<>;g",title);
Dprintf("\p;log;g",spec->name);
}
#endif
return(PtrPlusHand_(spec,(*messH)->etlFiles,sizeof(*spec)));
}
return noErr;
}
/************************************************************************
* CleanSpoolFolder - clean up the spool folder
************************************************************************/
OSErr CleanSpoolFolder(uLong age)
{
CInfoPBRec hfi;
FSSpec spec;
if (SubFolderSpec(SPOOL_FOLDER,&spec))
return(noErr);
Zero(hfi);
hfi.hFileInfo.ioNamePtr = spec.name;
age = LocalDateTime()-24*3600*age;
while (!DirIterate(spec.vRefNum,spec.parID,&hfi))
{
if (EventPending()) return(userCanceledErr);
MiniEvents();
if (AllDigits(spec.name+1,*spec.name)) // we have a spool folder
if (hfi.dirInfo.ioDrMdDat<age)
if (!RemoveDir(&spec)) hfi.hFileInfo.ioFDirIndex--;
}
return(noErr);
}
/************************************************************************
* AppendMessText - stick some text after one of the fields of a message.
************************************************************************/
OSErr AppendMessText(MessHandle messH,short whichTXE,UPtr string,long size)
{
HeadSpec hs;
if (CompHeadFind(messH,whichTXE,&hs))
{
return(CompHeadAppendPtr(TheBody,&hs,string,size));
}
else return(fnfErr);
}
/************************************************************************
* MessPlainBytes - make sure bytes are plain
************************************************************************/
OSErr MessPlainBytes(MessHandle messH,short whichTXE,short bytes)
{
OSErr err = noErr;
HeadSpec hs;
long start,stop;
if (!CompHeadFind(messH,whichTXE,&hs))
err = fnfErr;
else
{
if (bytes<0)
{
start = hs.stop+bytes;
start = MAX(start,hs.value);
stop = hs.stop;
}
else
{
start = hs.value;
stop = start+bytes;
}
PeteParaConvert(TheBody,start,stop);
PetePlain(TheBody,start,stop,peAllValid);
PeteParaRange(TheBody,&start,&stop);
if (!whichTXE && bytes<0) PetePlainPara(TheBody,kPETELastPara);
PetePlainParaAt(TheBody,start,stop);
}
return err;
}
/************************************************************************
* DoIterativeThingyLo - do something over all selected messages
************************************************************************/
void DoIterativeThingyLo(TOCHandle tocH,int item,long modifiers,TextAddrHandle addr,Boolean warnings)
{
int sumNum;
short toWhom = (short) addr;
short lastSelected = -1;
Str255 title;
FSSpec trashSpec;
long count;
long size;
long gran;
uLong pTicks = TickCount();
Boolean nuke = item==MESSAGE_DELETE_ITEM&&((*tocH)->which==TRASH || (optionKey|shiftKey)==(modifiers&(optionKey|shiftKey)))&&(!(*tocH)->imapTOC||PrefIsSet(PREF_ALLOW_IMAP_NUKE));
#ifdef THREADING_ON
Boolean busy = false;
#endif
uLong oldEzOpenSerialNum = (*tocH)->previewID ? (*tocH)->ezOpenSerialNum: 0;
#ifdef PERF
PerfControl(ThePGlobals,True);
#endif PERF
#ifdef IMAP
if (item==MESSAGE_DELETE_ITEM && (*tocH)->imapTOC && !nuke)
{
Handle uids = nil;
long c = CountSelectedMessages(tocH);
long sumNum;
// only do something if there are some selected messages
if (c)
{
// build a list of uids to be deleted
uids = NuHandleClear(c*sizeof(unsigned long));
if (uids)
{
LDRef(tocH);
for (sumNum=0;sumNum<(*tocH)->count && c;sumNum++)
if ((*tocH)->sums[sumNum].selected)
{
// Close this message if it's open
if ((*tocH)->sums[sumNum].messH) CloseMyWindow(GetMyWindowWindowPtr((*(*tocH)->sums[sumNum].messH)->win));
BMD(&((*tocH)->sums[sumNum].uidHash),&((unsigned long *)(*uids))[--c],sizeof(unsigned long));
}
UL(tocH);
// and delete them.
IMAPDeleteMessages(tocH, uids, nuke, false, false, false);
// preview the next message ...
if (oldEzOpenSerialNum && (sumNum=FindSumBySerialNum(tocH,oldEzOpenSerialNum))>=0 && !(modifiers&shiftKey))
Preview(tocH,sumNum);
}
else
{
WarnUser(MemError(), MEM_ERR);
}
}
return;
}
#endif
if (item==MESSAGE_DELETE_ITEM && !nuke)
{
trashSpec.vRefNum = MailRoot.vRef;
trashSpec.parID = MailRoot.dirId;
GetRString(trashSpec.name,TRASH);
MoveSelectedMessagesLo(tocH,&trashSpec,False,True,true,warnings);
if (oldEzOpenSerialNum && (sumNum=FindSumBySerialNum(tocH,oldEzOpenSerialNum))>=0 && !(modifiers&shiftKey))
Preview(tocH,sumNum);
(*tocH)->userActive = !(modifiers&shiftKey);
return;
}
if (nuke && warnings && SelectedWarnings(tocH,True,True)) return;
// memory preflight
count = CountSelectedMessages(tocH);
if (item==MESSAGE_FORWARD_ITEM || item==MESSAGE_REDISTRIBUTE_ITEM || item==MESSAGE_REPLY_ITEM || item==MESSAGE_SALVAGE_ITEM)
{
if (item==MESSAGE_REDISTRIBUTE_ITEM && PrefIsSetOrNot(PREF_TURBO_REDIRECT,modifiers,optionKey) && toWhom)
; // nevermind, turbo redirect
else
{
size = count * 7 K; // account just for the windows
if (item!=MESSAGE_REPLY_ITEM || !(modifiers&shiftKey))
size += SizeSelectedMessages(tocH,true);
if (MemoryPreflight(size)) return; // too much memory asked for
if (count > GetRLong(WIN_GEN_WARNING_THRESH))
if (!MultiMessageOpOK(WIN_GEN_WARNING,count))
return;
}
}
#ifdef TWO
if (item == STATE_HIER_MENU)
toWhom = Item2Status(toWhom);
#endif
/*
* progress stuff
*/
switch (item)
{
case PERS_HIER_MENU:
//No Personalities menu in Light
if (HasFeature (featureMultiplePersonalities))
gran = (*tocH)->which==OUT ? 1 : 50;
break;
case MESSAGE_DELETE_ITEM:
gran = 10;
break;
case STATE_HIER_MENU:
case LABEL_HIER_MENU:
case PRIOR_HIER_MENU:
case TABLE_HIER_MENU:
case SERVER_HIER_MENU:
gran = 50;
break;
default:
gran = 1;
break;
}
#ifdef IMAP
if ((*tocH)->imapTOC // some IMAP operations will open their own progress window
&& (item==MESSAGE_DELETE_ITEM // no progress for deletes from the message menu, or server menu options
|| (item == SERVER_HIER_MENU)));
else
{
#endif
if (count>gran) OpenProgress();
ProgressMessageR(kpSubTitle,LEFT_TO_PROCESS);
Progress(NoBar,count,nil,nil,nil);
#ifdef IMAP
}
#endif
if (!nuke && item==MESSAGE_DELETE_ITEM) AddXfUndo(tocH,GetTrashTOC(),-1);
for (sumNum=(*tocH)->count-1;sumNum>=0;sumNum--)
{
if ((*tocH)->sums[sumNum].selected)
{
Boolean doVirtualMB;
TOCHandle realTOC;
short realSum;
lastSelected = sumNum;
MakeMessTitle(title,tocH,sumNum,False);
if (count>1) MiniEvents();
if (CommandPeriod) break;
if (!(--count%gran) || TickCount()-pTicks>30)
{
Progress(NoBar,count,nil,nil,title);
pTicks = TickCount();
}
realTOC = GetRealTOC(tocH,sumNum,&realSum);
if (nuke && realTOC == tocH)
SearchUpdateSum(tocH,sumNum,tocH,(*tocH)->sums[sumNum].serialNum,false,true);
doVirtualMB = DoMessageMenu(item,tocH,sumNum,toWhom,addr,modifiers,nuke,&busy);
if (!nuke && realTOC == tocH && doVirtualMB)
// Check for updates to search results
SearchUpdateSum(tocH,sumNum,tocH,(*tocH)->sums[sumNum].serialNum,false,false);
if (realTOC && realTOC != tocH && doVirtualMB)
// do real mailbox also if working in virtual mailbox
DoMessageMenu(item,realTOC,realSum,toWhom,addr,modifiers,nuke,&busy);
#ifdef IMAP
// hack: The Delete from Server, Fetch Message Text, and Fetch Attachments menu choices handle all selected messages for IMAP boxes
if ((*tocH)->imapTOC && (item == SERVER_HIER_MENU) && ((toWhom == isvmDelete) || (toWhom == isvmFetchMessage) || (toWhom == isvmFetchAttachments))) break;
#endif
}
MonitorGrow(True);
}
out:
CloseProgress();
ShowBoxSizes((*tocH)->win);
#ifdef THREADING_ON
if (busy)
WarnUser (SENDING_WARNING, 0);
#endif
if (!CommandPeriod)
{
#ifdef TWO
if (item==MESSAGE_REDISTRIBUTE_ITEM &&
PrefIsSetOrNot(PREF_TURBO_REDIRECT,modifiers,optionKey) &&
!(modifiers&shiftKey)
&& toWhom)
DoIterativeThingy(tocH,MESSAGE_DELETE_ITEM,0,0);
#endif
BoxSelectAfter((*tocH)->win,lastSelected);
}
if (oldEzOpenSerialNum && !(*tocH)->previewID && (sumNum=FindSumBySerialNum(tocH,oldEzOpenSerialNum))>=0 && !(modifiers&shiftKey))
Preview(tocH,sumNum);
CheckBox(GetWindowMyWindowPtr(FrontWindow_()),False);
#ifdef PERF
PerfControl(ThePGlobals,False);
#endif
}
/************************************************************************
* DoMessageMenu - do something to a messages
************************************************************************/
Boolean DoMessageMenu(short item,TOCHandle tocH,short sumNum,short toWhom,TextAddrHandle addr,long modifiers,Boolean nuke,Boolean *busy)
{
MyWindowPtr win;
Boolean doVirtualMB = true;
Str255 s;
switch(item)
{
case MESSAGE_DELETE_ITEM:
DeleteMessageLo(tocH,sumNum,nuke);
break;
case STATE_HIER_MENU:
#ifdef THREADING_ON
if ((*tocH)->sums[sumNum].state==BUSY_SENDING)
*busy = true;
else
#endif
{
SetState(tocH,sumNum,toWhom);
doVirtualMB = false; // Already took care of this
}
break;
case PERS_HIER_MENU:
//No personalities menu in Light
if (HasFeature (featureMultiplePersonalities))
SetPers(tocH,sumNum,FindPersByName(MyGetItem(GetMHandle(item),toWhom,s)),True);
break;
case LABEL_HIER_MENU:
SetSumColor(tocH,sumNum,Menu2Label(toWhom));
doVirtualMB = false; // Already took care of this
break;
case SERVER_HIER_MENU:
#ifdef IMAP
ServerMenuChoice(tocH,sumNum,toWhom, (modifiers&shiftKey)!=0);
#else
ServerMenuChoice(tocH,sumNum,toWhom);
#endif
break;
case TABLE_HIER_MENU:
SetMessTable(tocH,sumNum,toWhom);
break;
case FILE_MENU:
ExportHTMLSum(tocH,sumNum);
break;
case PRIOR_HIER_MENU:
SetPriority(tocH,sumNum,NewPrior(toWhom,(*tocH)->sums[sumNum].priority));
doVirtualMB = false; // Already took care of this
break;
default:
#ifdef IMAP
// must have the message before we can do anything here.
// right now, fetch the entire message, including attachments, unless we're just replying.
if (EnsureMsgDownloaded(tocH,sumNum,item!=MESSAGE_REPLY_ITEM))
{
#endif
if (win = GetAMessage(tocH,sumNum,nil,nil,False))
{
WindowPtr winWP = GetMyWindowWindowPtr(win);
switch(item)
{
case MESSAGE_SALVAGE_ITEM:
if (!DoSalvageMessage(win,False)) CommandPeriod=true;
break;
case MESSAGE_FORWARD_ITEM:
if (!DoForwardMessage(win,addr,True)) CommandPeriod=true;
break;
case MESSAGE_REPLY_ITEM:
{
Boolean all, quote, self;
ReplyDefaults(modifiers,&all,&self,&quote);
if (!DoReplyMessage(win,all,self,quote,True,toWhom,True,True,True)) CommandPeriod=true;
}
break;
case MESSAGE_REDISTRIBUTE_ITEM:
if (!DoRedistributeMessage(win,addr,PrefIsSetOrNot(PREF_TURBO_REDIRECT,modifiers,optionKey),False,True)) CommandPeriod=true;
break;
}
if (!IsWindowVisible(winWP))
CloseMyWindow(winWP);
else
NotUsingWindow(winWP);
}
else CommandPeriod=true;
#ifdef IMAP
}
else CommandPeriod=true;
#endif
doVirtualMB = false;
break;
}
return doVirtualMB;
}
/************************************************************************
* ReplyDefaults - get the defaults for reply options
************************************************************************/
void ReplyDefaults(short modifiers,Boolean *all, Boolean *self, Boolean *quote)
{
*all = PrefIsSetOrNot(PREF_REPLY_ALL,modifiers,optionKey);
*self = !PrefIsSet(PREF_NOT_ME);
*quote = (modifiers&shiftKey) == 0;
}
/************************************************************************
* BoxNextSelected - find first selection in a mailbox
************************************************************************/
short BoxNextSelected(TOCHandle tocH,short afterNum)
{
int sNum, count;
count = (*tocH)->count;
for (sNum=afterNum+1;sNum<count;sNum++)
if ((*tocH)->sums[sNum].selected) return(sNum);
return(-1);
}
/**********************************************************************
* SaveTextAsMessage - save a text block as a message
**********************************************************************/
OSErr SaveTextAsMessage(Handle preText,Handle text,TOCHandle tocH,long *fromLen)
{
long size = GetHandleSize(text);
OSErr err;
if (size && (*text)[size-1]!='\015')
{
PtrPlusHand("\015",text,1);
size++;
}
err = SavePtrAsMessage(preText ? LDRef(preText):nil,preText?GetHandleSize(preText):nil,LDRef(text),size,tocH,fromLen);
UL(text);
if (preText) UL(preText);
return(err);
}
/**********************************************************************
* SavePtrAsMessage - save a text block as a message
**********************************************************************/
OSErr SavePtrAsMessage(UPtr preText,long preSize,UPtr text, long size, TOCHandle tocH,long *fromLen)
{
OSErr err;
long eof;
Str31 name;
LineIOD lid;
MSumType sum;
FSSpec spec;
GetMailboxName(tocH,-1,name);
/*
* open mailbox and write the bytes
*/
if (!(err=BoxFOpen(tocH)))
{
eof = FindTOCSpot(tocH,size);
err = SetFPos((*tocH)->refN,fsFromStart,eof);
if (!err) err = PutOutFromLine((*tocH)->refN,fromLen);
if (!err && preText) err = AWrite((*tocH)->refN,&preSize,preText);
if (!err) err = AWrite((*tocH)->refN,&size,text);
if (!err) err = TruncAtMark((*tocH)->refN);
TOCSetDirty(tocH,true);
BoxFClose(tocH,true);
}
/*
* did it work?
*/
if (err)
{
FileSystemError(WRITE_MBOX,name,err);
return(err);
}
/*
* read it back
*/
spec = GetMailboxSpec(tocH,-1);
if (err=OpenLine(spec.vRefNum,spec.parID,name,fsRdWrPerm,&lid))
return(FileSystemError(READ_MBOX,name,err));
if (err=SeekLine(eof,&lid)) return(FileSystemError(READ_MBOX,name,err));
ReadSum(nil,False,&lid,True);
Zero(sum);
if (!ReadSum(&sum,False,&lid,False))
err = !SaveMessageSum(&sum,&tocH);
else err = 1;
CloseLine(&lid);
return(err);
}
#pragma segment MsgOps
/************************************************************************
* DoSalvageMessage - glean what you can from a bounced message's headers
************************************************************************/
MyWindowPtr DoSalvageMessage(MyWindowPtr win,Boolean forXfer)
{
return DoSalvageMessageLo(win, forXfer, false);
}
/************************************************************************
* DoSalvageMessageLo - glean what you can from a bounced message's headers
************************************************************************/
MyWindowPtr DoSalvageMessageLo(MyWindowPtr win,Boolean forXfer,Boolean forIMAP)
{
WindowPtr winWP = GetMyWindowWindowPtr (win);
MessHandle origMessH = (MessHandle)GetMyWindowPrivateData(win);
MessHandle newMessH;
MyWindowPtr newWin;
WindowPtr newWinWP;
Str255 scratch;
short field;
HeadSpec oldHS, newHS;
OSErr err=noErr;
UHandle text;
long newBo;
Boolean html = !PrefIsSet(PREF_SEND_ENRICHED_NEW);
NicknameWatcherFocusChange (win->pte); /* MJN */
if (GetWindowKind(winWP)!=COMP_WIN && !SaveMessHi(win,False)) return(nil);
#ifdef IMAP
// Make sure message has been downloaded if IMAP
if (!EnsureMsgDownloaded((*origMessH)->tocH,(*origMessH)->sumNum,true))
return nil;
#endif
PushPers(PERS_FORCE(MESS_TO_PERS(origMessH)));
if (newWin=DoComposeNew(0))
{
long sigLen;
newMessH = (MessHandle)GetMyWindowPrivateData(newWin);
XferCustomTable(origMessH,newMessH);
if (GetWindowKind(winWP)==COMP_WIN || MessFlagIsSet(origMessH,FLAG_OUT))
{
//if (win->qWindow.windowKind!=COMP_WIN) AlignHeaders(origMessH);
for (field=0;!err && field<=ATTACH_HEAD;field++)
{
if (field!=FROM_HEAD ||
!CompHeadGetStr(origMessH,field,scratch) && (MessOptIsSet(origMessH,OPT_REDIRECTED) || IsMe(scratch)))
{
if (CompHeadFind(origMessH,field,&oldHS))
{
// If we have the body, the oldHS will not include the sig
// This is bad for copying
// However, we do need to remember this fact, so we can
// make sure we lock things correctly later
if (field==0)
{
sigLen = PeteLen((*origMessH)->bodyPTE) - oldHS.stop;
oldHS.stop += sigLen;
}
if (oldHS.value<oldHS.stop)
{
if (CompHeadFind(newMessH,field,&newHS))
{
if (field==FROM_HEAD)
err = CompHeadSetStr((*newMessH)->bodyPTE,&newHS,scratch);
else
err = PeteCopy((*origMessH)->bodyPTE,(*newMessH)->bodyPTE,
oldHS.value,oldHS.stop,newHS.stop,nil,field);
}
if (!err && CompHeadFind(newMessH,field,&newHS))
{
PeteLock((*newMessH)->bodyPTE,newHS.value,newHS.stop,0);
if (field==0 && sigLen)
PeteLock((*newMessH)->bodyPTE,newHS.stop-sigLen,newHS.stop-sigLen+*GetRString(scratch,SIG_INTRO)+1,peModLock|peSelectLock|peClickBeforeLock);
}
}
}
}
}
TOCSetDirty((*newMessH)->tocH,true);
SetSumColor((*newMessH)->tocH,(*newMessH)->sumNum,GetSumColor((*origMessH)->tocH,(*origMessH)->sumNum));
SumOf(newMessH)->flags = SumOf(origMessH)->flags;
SumOf(newMessH)->opts = SumOf(origMessH)->opts;
SumOf(newMessH)->sigId = SigValidate(SumOf(origMessH)->sigId);
SumOf(newMessH)->origPriority = SumOf(newMessH)->priority = SumOf(origMessH)->priority;
if (GetWindowKind(winWP)!=COMP_WIN && MessOptIsSet(newMessH,OPT_INLINE_SIG))
AddInlineSig(newMessH);
if (GetWindowKind(winWP)==COMP_WIN && (*origMessH)->hTranslators)
{
text = DupHandle((Handle)(*origMessH)->hTranslators);
(*newMessH)->hTranslators = (void*)text;
}
else if (!GetRHeaderAnywhere(origMessH,HEADER_STRN+TRANSLATOR_HEAD,&text))
{
AddTranslatorsFromPtr(newMessH,LDRef(text),GetHandleSize(text));
ZapHandle(text);
}
}
else
{
UPtr spot, oldSpot, beginning, end;
long size, total;
Boolean toFound;
Str63 received;
long offset;
if (SumOf(origMessH)->flags & FLAG_OUT)
{
SumOf(newMessH)->flags = SumOf(origMessH)->flags;
SumOf(newMessH)->sigId = SumOf(origMessH)->sigId;
}
PeteGetTextAndSelection((*origMessH)->bodyPTE,&text,nil,nil);
beginning = spot = LDRef(text);
total = size = GetHandleSize_(text);
end = spot+size;
if (forXfer || SumOf(origMessH)->flags & FLAG_OUT) oldSpot = nil;
else
{
/*
* find the last "Received:" header
*/
GetRString(received,RECEIVED_HEAD); TrimWhite(received);
oldSpot = nil;
while (spot=FindHeaderString(spot,received,&size,True))
{
oldSpot=spot;
spot += size;
size = end - spot;
}
}
/*
* copy the relevant parts
*/
if (!oldSpot) oldSpot=beginning;
{
if (oldSpot!=beginning)
SumOf(newMessH)->sigId = SIG_NONE;
size = total - (oldSpot-beginning);
TextFindAndCopyHeader(oldSpot,size,newMessH,HeaderName(SUBJ_HEAD),SUBJ_HEAD,0);
TextFindAndCopyHeader(oldSpot,size,newMessH,HeaderName(CC_HEAD),CC_HEAD,0);
TextFindAndCopyHeader(oldSpot,size,newMessH,HeaderName(ATTACH_HEAD),ATTACH_HEAD,0);
toFound = TextFindAndCopyHeader(oldSpot,size,newMessH,HeaderName(TO_HEAD),TO_HEAD,0);
TextFindAndCopyHeader(oldSpot,size,newMessH,HeaderName(BCC_HEAD),BCC_HEAD,0);
/*
* find the body
*/
for (spot=oldSpot;spot<oldSpot+size-1;spot++)
if (spot[0]=='\015' && spot[1]=='\015') break;
spot += 2;
offset = spot-beginning;
UL(text);
if (spot<oldSpot+size-1)
{
newBo = PETEGetTextLen(PETE,(*newMessH)->bodyPTE);
err = PeteCopyNoLabel((*origMessH)->bodyPTE,(*newMessH)->bodyPTE,
offset,total,newBo,nil,False,html?0:0xffffffff);
//err = SetMessText(newMessH,0,spot,total-(spot-beginning));
if (!err) PeteLock((*newMessH)->bodyPTE,newBo,0x7fffffff,0);
}
if (MessFlagIsSet(origMessH,FLAG_HAS_ATT))
{
CopyAttachments(newMessH);
SpoolAttachments(newMessH);
}
}
UL(text);
}
newWinWP = GetMyWindowWindowPtr(newWin);
if (err && newWin)
{
NoSaves = True; CloseMyWindow(newWinWP); NoSaves = False;
WarnUser(PETE_ERR,err);
PopPers();
return(nil);
}
if (!forXfer || forIMAP)
{
WeedXAttachments(newMessH, !forIMAP);
if (UseInlineSig && !MessOptIsSet(newMessH,OPT_INLINE_SIG)) AddInlineSig(newMessH);
UpdateSum(newMessH,SumOf(newMessH)->offset,SumOf(newMessH)->length);
if(!forIMAP)
{
ShowMyWindow(newWinWP);
newWin->isDirty = False;
PeteCleanList(newWin->pte);
}
}
}
PopPers();
return(newWin);
}
/************************************************************************
* UniqueHeader - make sure the addresses in a header are unique
************************************************************************/
OSErr UniqueHeader(MessHandle messH,short head,Boolean wantErrors)
{
Handle addresses = nil;
short oldSize;
HeadSpec hs;
OSErr err = noErr;
if (CompHeadFind(messH,head,&hs) && !CompHeadGetText(TheBody,&hs,&addresses))
{
oldSize = GetHandleSize_(addresses);
err = NickUniq(addresses,"\p, ",wantErrors);
if (oldSize != GetHandleSize_(addresses))
CompHeadSet(TheBody,&hs,addresses);
ZapHandle(addresses);
}
return err;
}
/************************************************************************
* RemoveSelf - Remove "me" from a list of addresses
* Ray Davison, SFU
************************************************************************/
OSErr RemoveSelf(MessHandle messH,short head,Boolean wantErrors)
{
Str63 dummy;
Str255 temp;
EAL_VARS_DECL;
UHandle rawMyself=nil, cookedMyself=nil;
UHandle rawAddress=nil, spewHandle=nil;
UHandle myself=NuHTempBetter(0);
long offset, meOffset;
Boolean removed = False;
Handle text=nil;
Handle oldText=nil;
HeadSpec hs;
Boolean group;
Boolean groupWas = False;
OSErr err = noErr;
/* Get a definition of who I am */
GetRString(temp, ME);
PtrPlusHand_(temp+1, myself, temp[0]);
PtrPlusHand_(",", myself, 1);
GetPOPInfo(temp, dummy);
PtrPlusHand_(temp+1, myself, temp[0]);
PtrPlusHand_(",", myself, 1);
GetReturnAddr(temp ,True);
PtrPlusHand_(temp+1, myself, temp[0]);
if (!(err=SuckAddresses(&rawMyself,myself, False, wantErrors, False,nil)) &&
!(err=ExpandAliasesLow(&cookedMyself,rawMyself, 0, False, "",EAL_VARS))) // no autoqual
{
ZapHandle(rawMyself);
ZapHandle(myself);
/* expand the text */
if (CompHeadFind(messH,head,&hs) && !CompHeadGetText(TheBody,&hs,&oldText))
{
if (!(err=SuckAddresses(&rawAddress,oldText, True, wantErrors, False,nil)) && (text=NuHTempBetter(0L)))
{
/* Remove myself from address */
if (rawAddress && cookedMyself)
{
LDRef(cookedMyself);
for (offset=0; (*rawAddress)[offset]; offset += (*rawAddress)[offset]+2)
{
/* clean up the address */
LDRef(rawAddress);
SuckPtrAddresses(&spewHandle,(*rawAddress)+offset+1,
(*rawAddress)[offset], False, wantErrors, true,nil);
UL(rawAddress);
if (spewHandle)
{
LDRef(spewHandle);
group = (*spewHandle)[**spewHandle]==':';
/* look for this in the "me" addresses */
for (meOffset=0; (*cookedMyself)[meOffset]; meOffset += (*cookedMyself)[meOffset]+2)
{
if (StringSame(*spewHandle,*cookedMyself+meOffset))
break;
}
ZapHandle(spewHandle);
}
/* if we didn't find it, then add this address to the result */
if (!(*cookedMyself)[meOffset])
{
LDRef(rawAddress);
if (!groupWas && GetHandleSize_(text)) PtrPlusHand_(", ", text, 2);
PtrPlusHand_((*rawAddress)+offset+1,text,(*rawAddress)[offset]);
HUnlock(rawAddress);
}
else removed = True;
groupWas = group;
}
}
}
}
}
ZapHandle(oldText);
ZapHandle(rawMyself);
ZapHandle(cookedMyself);
ZapHandle(rawAddress);
ZapHandle(spewHandle);
ZapHandle(myself);
if (removed) {
CompHeadSet(TheBody,&hs,text);
CompGatherRecipientAddresses (messH, true);
}
ZapHandle(text);
return err;
}
/**********************************************************************
* MessText - get the text of a message
**********************************************************************/
UHandle MessText(MessHandle messH)
{
UHandle text=nil;
PeteGetRawText(TheBody,&text);
return(text);
}
/**********************************************************************
* MessVisibleText - get only the visible text of a message
**********************************************************************/
UHandle MessVisibleText(MessHandle messH)
{
long runLen, offset;
UHandle text=nil;
PETEStyleEntry pse;
long len;
OSErr err = noErr;
Accumulator a;
err = PeteGetRawText(TheBody,&text);
if (err) return nil;
len = GetHandleSize(text);
Zero(pse);
Zero(a);
for (offset=0;offset<len;offset += runLen)
{
err = PeteGetStyle(TheBody, offset, &runLen, &pse);
if (err) break;
if (runLen==0)
{
offset++; // keep moving!
continue;
}
// add all text except invisible text (graphic with no handle)
if (!(pse.psGraphic && !pse.psStyle.graphicStyle.graphicInfo))
{
err = AccuAddFromHandle(&a,text,offset,runLen);
if (err) break;
}
}
if (err) AccuZap(a);
else AccuTrim(&a);
return a.data;
}
/************************************************************************
* ReopenMessage - reopen the current message
************************************************************************/
MyWindowPtr ReopenMessage(MyWindowPtr win)
{
WindowPtr winWP = GetMyWindowWindowPtr (win);
UHandle text;
MessHandle messH = Win2MessH(win);
OSErr err = noErr;
text = GetMessText(Win2MessH(win));
if (text)
{
// stick in the text
{
PeteSetTextPtr(TheBody,nil,0);
PeteKillUndo(TheBody);
PeteCalcOff(TheBody);
(*PeteExtra(TheBody))->emoDesired = !MessFlagIsSet(messH,FLAG_SHOW_ALL);
PetePlain(TheBody,kPETECurrentStyle,kPETECurrentStyle,peAllValid);
PetePlainPara(TheBody,0);
if (!MessFlagIsSet(messH,FLAG_SHOW_ALL) &&
(MessFlagIsSet(messH,FLAG_RICH)
#ifndef OLDHTML
|| MessOptIsSet(messH,OPT_HTML)
#endif
|| MessOptIsSet(messH,OPT_FLOW)
|| MessOptIsSet(messH,OPT_CHARSET)
))
{
err = InsertRichLo(text,0,-1,-1,True,True,TheBody,(*PeteExtra(win->pte))->partStack,messH,MessOptIsSet(messH,OPT_DELSP));
ZapHandle(text);
}
else
err = PETEInsertPara(PETE,TheBody,kPETELastPara,nil,text,nil);
if (!err)
{
PeteSmallParas(TheBody);
text = nil;
// align headers if need be
//if (!PrefIsSet(PREF_DONT_ALIGN_HEADERS)) AlignHeaders(messH);
#ifdef OLDHTML
// interpret rich text
if (!MessFlagIsSet(messH,FLAG_SHOW_ALL))
{
if (MessOptIsSet(messH,OPT_HTML))
err = PeteHTML(TheBody,SumOf(messH)->bodyOffset-(*messH)->weeded,0,True);
}
#endif
if (!err) HiliteOddReply(messH);
if (!err) PeteTrimTrailingReturns(TheBody,true);
if (!err)
{
// recalculate
PeteCalcOn(TheBody);
// add notification control if necessary
CheckAddNotifyControls(win, messH);
if (PrefIsSet(PREF_ZOOM_OPEN)) ReZoomMyWindow(winWP);
// scroll to correct position
if (!MessFlagIsSet(messH,FLAG_SHOW_ALL)) ShowMessageSeparator(TheBody,true);
InvalContent(win);
}
}
}
// mark document as clean
PETEMarkDocDirty(PETE,TheBody,False);
win->isDirty = False;
PeteSetURLRescan(TheBody,0);
// kill text if still here
ZapHandle(text);
if (err)
{
WarnUser(DOC_DAMAGED_ERR,err);
CloseMyWindow(winWP);
win=nil;
}
}
else
{
CloseMyWindow(winWP);
win = nil;
}
if (win) PeteCalcOn(TheBody);
return(win);
}
/************************************************************************
* FindFrom - find a (nicely formatted) From address
************************************************************************/
void FindFrom(UPtr who, PETEHandle pte)
{
UPtr found;
Str31 header;
long len;
Handle text;
PeteGetRawText(pte,&text);
len = GetHandleSize(text);
GetRString(header,FROM_HEAD+HEADER_STRN);
if (found = FindHeaderString(LDRef(text),header,&len,False))
{
*who = MIN(62,len);
BMD(found,who+1,*who);
who[*who+1] = 0;
BeautifyFrom(who);
}
else *who = 0;
UL(text);
}
/************************************************************************
* QuoteLines - put a quote prefix before the specified TextEdit lines
************************************************************************/
void QuoteLines(PETEHandle pte,long from,long to,short pfid, long *qEnd)
{
long this;
Str15 prefix;
UHandle text;
long count = 0;
Boolean first = True;
PETEStyleEntry pse;
RGBColor color;
Boolean withSpace = false;
long numSpaces = 0;
Byte quoteChar;
Zero(pse);
if (qEnd) *qEnd = to;
GetRString(prefix,pfid);
if (!*prefix) return;
// if trailing space, remove and note
if (*prefix==2 && prefix[2]==' ')
{
withSpace = true;
--*prefix;
quoteChar = prefix[1];
}
PETEGetRawText(PETE,pte,&text);
this = GetHandleSize(text);
to = MIN(to,this);
for (this=to-2;this>=from;this--)
if (!this || (*text)[this]=='\015')
{
if (withSpace && (*text)[this+1]!=quoteChar)
{
PeteInsertChar(pte,this?this+1:this,' ',nil);
numSpaces++;
}
PeteInsertPtr(pte,this ? this+1:this,prefix+1,*prefix);
count++;
}
if (qEnd) *qEnd = to + count * *prefix + numSpaces; // adjust for inserted prefixes
// do the scanner's work for it
if (!Black(GetRColor(&color,QUOTE_COLOR)))
{
pse.psStyle.textStyle.tsLabel = pQuoteLabel;
PETESetTextStyle(PETE,pte,from,to + count * *prefix,&pse.psStyle.textStyle,peLabelValid);
}
}
/************************************************************************
* PrependMessText - stick some text before one of the fields of a message.
************************************************************************/
OSErr PrependMessText(MessHandle messH,short whichTXE,UPtr string,long size)
{
HeadSpec hs;
if (CompHeadFind(messH,whichTXE,&hs))
return(CompHeadPrependPtr(TheBody,&hs,string,size));
else return(fnfErr);
}
/************************************************************************
* FindHeaderString - pick the given header out of a message, by name
* Pointer returned is to
************************************************************************/
UPtr FindHeaderString(UPtr text,UPtr headerName,long *size,Boolean bodyToo)
{
UPtr spot,end, colon;
char header[MAX_HEADER];
short mLen = MIN(MAX_HEADER-2,*headerName);
#ifdef TWO
Boolean any = EqualStrRes(headerName,FILTER_ANY);
Boolean addressee = EqualStrRes(headerName,FILTER_ADDRESSEE);
static UHandle addrRes;
#endif
if (addressee) mLen = MAX_HEADER-2;
#ifdef TWO
if (!addrRes || !*addrRes)
{
if (addrRes) LoadResource(addrRes);
else addrRes = GetResource_('STR#',AddrHeadsStrn);
if (!addrRes || !*addrRes) return(nil);
HNoPurge(addrRes);
}
#endif
for (end = text+*size; text<end; text = spot+1)
{
for (spot=text;spot<end;spot++) if (*spot == '\015' && (spot==end-1||!IsWhite(spot[1]))) break;
if (spot==text && !bodyToo) break;
*header = MIN(mLen,end-text);
BMD(text,header+1,*header);
#ifdef TWO
if (addressee)
{
if (colon=PIndex(header,':'))
*header = colon-header;
}
#endif
if (
#ifdef TWO
any ||
addressee && FindSTRNIndexRes(addrRes,header) ||
#endif
EqualString(header,headerName,False,True))
{
#ifdef TWO
if (!any && !addressee)
#endif
text += *header;
while (IsWhite(*text)) text++;
for (spot--; IsWhite(*spot); spot--);
*size = MAX(0,spot-text+1);
return(text);
}
}
return(nil);
}
/************************************************************************
* DoReplyMessage - craft a reply to a message
************************************************************************/
MyWindowPtr DoReplyMessage(MyWindowPtr win, Boolean all, Boolean self, Boolean quote, Boolean doFcc, short withWhich,Boolean vis,Boolean station,Boolean caching)
{
WindowPtr winWP = GetMyWindowWindowPtr (win);
MessHandle origMessH = (MessHandle)GetMyWindowPrivateData(win);
MessHandle newMessH;
Str255 subj,scratch,replyTo;
long bodyOffset = -1;
MyWindowPtr newWin=nil;
WindowPtr newWinWP;
short r;
long len;
Handle text;
long origLen;
long selStart, selEnd;
Str255 soundName;
Boolean doSound;
HeadSpec hs;
OSErr err = noErr;
PETETextStyle style;
long newBo;
//PeteSaneMargin marg;
Boolean rich;
Boolean html = !PrefIsSet(PREF_SEND_ENRICHED_NEW);
PETEHandle copyFromPTE;
TOCHandle origTOCH;
Boolean listReply = MessOptIsSet(origMessH,OPT_BULK) && PrefIsSet(PREF_LIST_REPLYTO);
Boolean cacheReplyTo = !PrefIsSet (PREF_NICK_CACHE) && !PrefIsSet (PREF_NICK_CACHE_NOT_ADD_REPLY_TO);
Boolean wantErrors = true;
// Dunno why this was done
// if (GetWindowKind(winWP)!=COMP_WIN && !SaveMessHi(win,False)) return(nil);
#ifdef IMAP
// Make sure message has been downloaded if IMAP. Don't care about attachments.
if (!EnsureMsgDownloaded((*origMessH)->tocH,(*origMessH)->sumNum,false))
return nil;
#endif
PushPers(PERS_FORCE(MESS_TO_PERS(origMessH)));
if (newWin=DoComposeNew(0))
{
newWinWP = GetMyWindowWindowPtr(newWin);
newMessH = (MessHandle) GetMyWindowPrivateData(newWin);
replyTo[0] = 0;
rich = UseFlowOutExcerpt || (MessIsRich(origMessH) || MessIsRich(newMessH));
XferCustomTable(origMessH,newMessH);
PeteGetTextAndSelection((*origMessH)->bodyPTE,&text,&selStart,&selEnd);
origLen = GetHandleSize(text);
/* handle the subject */
FindAndCopyHeader(origMessH,newMessH,HeaderName(SUBJ_HEAD),SUBJ_HEAD);
GetRString(scratch,REPLY_INTRO);
TrimWhite(scratch);
CompHeadGetStr(newMessH,SUBJ_HEAD,subj);
if (!ReMatch(subj,scratch))
{
GetRString(scratch,REPLY_INTRO);
PrependMessText(newMessH,SUBJ_HEAD,scratch+1,*scratch);
}
/* reply to sender */
replyTo[0] = 0;
if (listReply && !all) {
FindAndCopyHeader(origMessH,newMessH,HeaderName(FROM_HEAD),TO_HEAD);
if (HasFeature (featureNicknameWatching) && cacheReplyTo)
SuckHeaderText (origMessH, replyTo, sizeof (replyTo), FROM_HEAD);
}
else
for (r=1;*GetRString(scratch,ReplyStrn+r);r++)
{
TrimWhite(scratch);
if (FindAndCopyHeader(origMessH,newMessH,scratch,TO_HEAD))
{
doSound = MessOptIsSet(origMessH,OPT_WEIRD_REPLY);
if (!doSound) listReply = false; // as you were
// (jp) added to grab the reply to address
if (!PrefIsSet (PREF_NO_SPOKEN_WARNINGS) || cacheReplyTo)
SuckHeaderText (newMessH, replyTo, sizeof (replyTo), TO_HEAD);
break;
}
}
if (caching && HasFeature (featureNicknameWatching) && cacheReplyTo && replyTo[0])
CacheRecentNickname (replyTo);
/* bring over the other recipients and cc's, if desired */
if (all)
{
FindAndCopyHeader(origMessH,newMessH,HeaderName(TO_HEAD),PrefIsSet(PREF_CC_REPLY)?CC_HEAD:TO_HEAD);
FindAndCopyHeader(origMessH,newMessH,HeaderName(CC_HEAD),CC_HEAD);
// If we've found a reply-to header and we have a mailing list, also copy From to the cc: field
if (doSound && listReply)
FindAndCopyHeader(origMessH,newMessH,HeaderName(FROM_HEAD),CC_HEAD);
/* remove self, if desired */
if (!self)
{
if (RemoveSelf(newMessH,TO_HEAD,wantErrors)) wantErrors=false;
if (RemoveSelf(newMessH,CC_HEAD,wantErrors)) wantErrors=false;
}
if (UniqueHeader(newMessH,TO_HEAD,wantErrors)) wantErrors=false;
UniqueHeader(newMessH,CC_HEAD,wantErrors);
}
/*
* for Larry's benefit
*/
if (PrefIsSet(PREF_NEWSGROUP_HANDLING)) CopyNewsgroups(origMessH,newMessH);
/*
* auto-fcc
*/
//Folder Carbon Copy - don't allow the auto FCC in Light
#ifdef IMAP
if (HasFeature (featureFcc) && doFcc && PrefIsSet(PREF_AUTO_FCC) && !(*(*origMessH)->tocH)->which && !IMAPDontAutoFccMailbox((*origMessH)->tocH))
#else
if (HasFeature (featureFcc) && doFcc && PrefIsSet(PREF_AUTO_FCC) && !(*(*origMessH)->tocH)->which)
#endif
{
FSSpec spec = GetMailboxSpec((*origMessH)->tocH,-1);
Fcc(newMessH,&spec);
}
/*
* in-reply-to and references
*/
ReplyReferences(origMessH,newMessH);
/*
* copy the body. Use the preview pane if that's what we have
*/
copyFromPTE = (*origMessH)->bodyPTE;
origTOCH = (*origMessH)->openedFromTocH ? (*origMessH)->openedFromTocH : (*origMessH)->tocH;
if ((*origTOCH)->win==GetWindowMyWindowPtr(FrontWindow_()) && (*origTOCH)->previewID &&
(*origTOCH)->previewID==(*origMessH)->openedFromSerialNum &&
!quote && *PeteSelectedString(scratch,(*origTOCH)->previewPTE))
{
copyFromPTE = (*origTOCH)->previewPTE;
PeteGetTextAndSelection(copyFromPTE,&text,&selStart,&selEnd);
}
if (quote)
{
bodyOffset = 0;
while (bodyOffset<origLen-1)
{
if ((*text)[bodyOffset+1]!='\015') bodyOffset += 2;
else if ((*text)[bodyOffset]=='\015') break;
else bodyOffset++;
}
while (bodyOffset<origLen && (*text)[bodyOffset] == '\015') bodyOffset++;
len = origLen-bodyOffset;
}
else
{
if (selEnd != selStart)
{
bodyOffset = selStart;
len = selEnd - bodyOffset;
}
}
#ifdef DEBUG
if (BUG14) {InvalContent(win);UpdateMyWindow(winWP);}
#endif
if (len > 0 && bodyOffset >= 0)
{
while (len && (*text)[bodyOffset+len-1]=='\015') len--;
newBo = PETEGetTextLen(PETE,(*newMessH)->bodyPTE);
if (len>0) err = PeteCopyNoLabel(copyFromPTE,(*newMessH)->bodyPTE,bodyOffset,bodyOffset+len,newBo,nil,!rich,html?0:0xffffffff);
HUnlock(text);
style.tsLock = 0;
#ifdef DEBUG
if (BUG14) {InvalContent(win);UpdateMyWindow(winWP);}
#endif
PETESetTextStyle(PETE,(*newMessH)->bodyPTE,newBo,0x7fffffff,&style,peLockValid);
if (rich) PeteExcerpt((*newMessH)->bodyPTE,newBo,PETEGetTextLen(PETE,(*newMessH)->bodyPTE)-1);
#ifdef DEBUG
if (BUG14) {InvalContent(win);UpdateMyWindow(winWP);}
#endif
if (err)
{
NoSaves = True; CloseMyWindow(newWinWP); NoSaves = False;
PopPers();
WarnUser(PETE_ERR,err);
return(nil);
}
/*
* make sure the last line is blank
*/
EnsureMessNewline(newMessH);
#ifdef DEBUG
if (BUG14) {InvalContent(win);UpdateMyWindow(winWP);}
#endif
if (rich) PetePlainPara((*newMessH)->bodyPTE,PeteParaAt((*newMessH)->bodyPTE,PETEGetTextLen(PETE,(*newMessH)->bodyPTE)));
#ifdef DEBUG
if (BUG14) {InvalContent(win);UpdateMyWindow(winWP);}
#endif
/*
* quote them
*/
if (!rich)
{
CompHeadFind(newMessH,0,&hs);
QuoteLines((*newMessH)->bodyPTE,hs.start,hs.stop-1,QUOTE_PREFIX,nil);
}
else if (PrefIsSet(PREF_SCHEERDER))
{
CompHeadFind(newMessH,0,&hs);
PeteSelect(nil,(*newMessH)->bodyPTE,hs.value,PeteLen((*newMessH)->bodyPTE));
PeteWrap((*newMessH)->win,(*newMessH)->bodyPTE,false);
}
#ifdef DEBUG
if (BUG14) {InvalContent(win);UpdateMyWindow(winWP);}
#endif
/*
* annotate
*/
if (rich) Attribute(QUOTH,origMessH,newMessH,False);
Attribute(all ? ATTRIBUTION:REP_SEND_ATTR,origMessH,newMessH,False);
if (rich) Attribute(UNQUOTH,origMessH,newMessH,True);
}
#ifdef DEBUG
if (BUG14) {InvalContent(win);UpdateMyWindow(winWP);}
#endif
/*
* copy priority (not my idea)
*/
if (!PrefIsSet(PREF_NO_XF_PRIOR))
SumOf(newMessH)->origPriority = SumOf(newMessH)->priority =
SumOf(origMessH)->origPriority;
SumOf(newMessH)->outType = OUT_REPLY; // for statistics
/*
* encryption
*/
if (MessOptIsSet(origMessH,OPT_WIPE)) SetMessFlag(newMessH,FLAG_ENCRYPT);
if (station)
{
if (!withWhich) ApplyDefaultStationery(newWin,True,True);
//Stationery - no support for this in Light
else if (HasFeature (featureStationery)) ApplyIndexStationery(newWin,withWhich,True,True);
}
(*PeteExtra((*newMessH)->bodyPTE))->quoteScanned = -1; // don't need to run the scanner, quotelines did it already
if (vis)
{
// shall we select the quote?
quote = quote || selEnd-selStart>=GetRLong(OPEN_AT_END_THRESH);
(*newMessH)->openToEnd = !quote;
AccuAddLong(&(*newMessH)->aSourceMID,SumOf(origMessH)->uidHash);
AccuAddLong(&(*newMessH)->aSourceMID,SumOf(origMessH)->state);
AccuAddLong(&(*newMessH)->aSourceMID,REPLIED);
SetState((*origMessH)->tocH,(*origMessH)->sumNum,REPLIED);
UpdateSum(newMessH,SumOf(newMessH)->offset,SumOf(newMessH)->length);
ShowMyWindow(newWinWP);
UpdateMyWindow(newWinWP);
if (doSound)
if (PrefIsSet (PREF_NO_SPOKEN_WARNINGS))
PlayNamedSound(GetRString(soundName,REPLY_SOUND));
else
(void) ComposeStdAlert (kAlertNoteAlert, PASSIVE_REPLY_TO_ASTR, replyTo);
}
newWin->isDirty = False;
PeteCleanList(newWin->pte);
}
PopPers();
return(newWin);
}
/**********************************************************************
* ReplyReferences
**********************************************************************/
OSErr ReplyReferences(MessHandle origMessH,MessHandle newMessH)
{
OSErr err = noErr;
UHandle /*t1=nil, */t2=nil, t3=nil;
Accumulator extras = (*newMessH)->extras;
long origLen = extras.offset;
/* Get the bodies of all three headers */
// GetRHeaderAnywhere(origMessH,HeaderStrn+IN_REPLY_TO_HEAD,&t1);
GetRHeaderAnywhere(origMessH,HeaderStrn+REFERENCES_HEAD,&t2);
GetRHeaderAnywhere(origMessH,HeaderStrn+MSGID_HEAD,&t3);
/* Strip returns - I don't know why we do this for IRT and MID, but whatever */
/* Should probably strip anything that's not an MID (like phrases) */
// if (t1)
// {
// SetHandleSize(t1,RemoveChar('\015',LDRef(t1),GetHandleSize(t1)));
// UL(t1);
// }
if (t2)
{
SetHandleSize(t2,RemoveChar('\015',LDRef(t2),GetHandleSize(t2)));
UL(t2);
}
if (t3)
{
SetHandleSize(t3,RemoveChar('\015',LDRef(t3),GetHandleSize(t3)));
UL(t3);
}
/* If there's a message ID, add IRT header */
if(t3)
{
/* Add the header and the colon */
if(!(err = AccuAddRes(&extras, HeaderStrn+IN_REPLY_TO_HEAD)))
/* Add a space */
if(!(err = AccuAddChar(&extras, ' ')))
/* Add the message ID */
if(!(err = AccuAddHandle(&extras, t3)))
/* Add a return */
err = AccuAddChar(&extras, '\015');
}
/* If there's any id's (from Refs, IRT, or MID), add Refs header */
if (!err && (/*t1 || */t2 || t3))
{
/* Add the header and the colon */
if(!(err = AccuAddRes(&extras, HeaderStrn+REFERENCES_HEAD)))
/* Add a space */
if(!(err = AccuAddChar(&extras, ' ')))
/* Add the Refs message IDs if they exist */
if (!t2 || !(err = AccuAddHandle(&extras,t2)))
{
// /* Add the IRT message ID if it exists */
// if (t1 /* && t1 does not appear in t2 */)
// /* Add a space if there were Refs message IDs */
// if (!t2 || !(err = AccuAddChar(&extras,' ')))
// err = AccuAddHandle(&extras,t1);
/* Add the MID if it exists */
if (!err && t3)
/* Add a space if there were Refs or IRT message IDs */
if ((/*!t1 && */!t2) || !(err = AccuAddChar(&extras,' ')))
err = AccuAddHandle(&extras,t3);
/* Add a return */
if (!err) err = AccuAddChar(&extras,'\015');
}
}
if (err) extras.offset = origLen;
AccuTrim(&extras);
(*newMessH)->extras = extras;
// ZapHandle(t1);
ZapHandle(t2);
ZapHandle(t3);
return(err);
}
/************************************************************************
* EnsureMessNewline - make sure a message ends in a newline
************************************************************************/
OSErr EnsureMessNewline(MessHandle messH)
{
Handle text;
long size;
OSErr err;
if (!(err=PETEGetRawText(PETE,TheBody,&text)))
{
size = GetHandleSize(text);
if (size<1||(*text)[size-1]!='\015')
err = PETEInsertTextPtr(PETE,TheBody,0x7fffffff,"\015\015",2,nil);
else if (!err && (size<2||(*text)[size-2]!='\015'))
err = PETEInsertTextPtr(PETE,TheBody,0x7fffffff,"\015",1,nil);
if (!err)
err = PETEInsertParaPtr(PETE,TheBody,kPETELastPara,nil,nil,0,nil);
MessPlainBytes(messH,0,-1);
}
return(err);
}
/************************************************************************
* Attribute - make an attribution
************************************************************************/
void Attribute(short attrId,MessHandle origMessH,MessHandle newMessH,Boolean atEnd)
{
Str255 attribution;
if (*GrabAttribution(attrId,(*origMessH)->win,attribution))
{
if (atEnd)
{
long endOfText = PeteLen((*newMessH)->bodyPTE);
PeteInsertPtr((*newMessH)->bodyPTE,endOfText-1,attribution+1,*attribution);
//if (MessIsRich(newMessH) || MessIsRich(origMessH)) //quote color leaking, so disable check
MessPlainBytes(newMessH,0,-(short)*attribution-1);
}
else
{
PCatC(attribution,'\015');
PrependMessText(newMessH,0,attribution+1,*attribution);
//if (MessIsRich(newMessH) || MessIsRich(origMessH)) //quote color leaking, so disable check
MessPlainBytes(newMessH,0,(short)*attribution);
}
}
}
/************************************************************************
* GrabAttribution - compute the attribution for a message
************************************************************************/
PStr GrabAttribution(short attrId,MyWindowPtr win,PStr attribution)
{
WindowPtr winWP = GetMyWindowWindowPtr (win);
Str255 template;
Str63 date,time;
Str127 who;
long secs;
long zone;
long sumNum;
MSumType sum;
*attribution = 0;
if (IsMessWindow(winWP))
sum = *SumOf(Win2MessH(win));
else if (GetWindowKind(winWP)==MBOX_WIN && (0<=(sumNum=LastMsgSelected(Win2TOC(win)))))
sum = (*Win2TOC(win))->sums[sumNum];
else return(attribution);
GetRString(template,attrId);
if (*template)
{
FindFrom(who,win->pte);
if (*who)
{
zone = /* whine, whine, whine PrefIsSet(PREF_LOCAL_DATE) ? ZoneSecs() : */ 60*sum.origZone;
secs = sum.seconds + zone;
TimeString(secs,False,attribution,nil);
FormatZone(date,zone);
ComposeRString(time,ATTR_TIME_FMT,attribution,date);
DateString(secs,shortDate,date,nil);
if (date[1]==optSpace) date[1] = ' ';
utl_PlugParams(template,attribution,who,date,sum.subj,time);
}
}
return(attribution);
}
/************************************************************************
* DoRedistributeMessage - craft a reply to a message
************************************************************************/
MyWindowPtr DoRedistributeMessage(MyWindowPtr win,TextAddrHandle toWhom,Boolean turbo,Boolean andDelete,Boolean showIt)
{
WindowPtr winWP = GetMyWindowWindowPtr (win);
MessHandle origMessH = (MessHandle)GetMyWindowPrivateData(win);
MessHandle newMessH;
Str255 scratch;
int bodyOffset;
MyWindowPtr newWin;
long newBo;
OSErr err;
PETETextStyle style;
Boolean html = !PrefIsSet(PREF_SEND_ENRICHED_NEW);
if (GetWindowKind(winWP)!=COMP_WIN && !SaveMessHi(win,False)) return(nil);
#ifdef IMAP
// Make sure message has been downloaded if IMAP
if (!EnsureMsgDownloaded((*origMessH)->tocH,(*origMessH)->sumNum,true))
return nil;
#endif
PushPers(PERS_FORCE(MESS_TO_PERS(origMessH)));
if (newWin=DoComposeNew(toWhom))
{
WindowPtr newWinWP = GetMyWindowWindowPtr (newWin);
newMessH = (MessHandle)GetMyWindowPrivateData(newWin);
XferCustomTable(origMessH,newMessH);
SetMessOpt(newMessH,OPT_REDIRECTED);
SetMessText(newMessH,FROM_HEAD,"",0);
FindAndCopyHeader(origMessH,newMessH,HeaderName(SUBJ_HEAD),SUBJ_HEAD);
FindAndCopyHeader(origMessH,newMessH,HeaderName(FROM_HEAD),FROM_HEAD);
if (MessFlagIsSet(origMessH,FLAG_OUT)) FindAndCopyHeader(origMessH,newMessH,HeaderName(ATTACH_HEAD),ATTACH_HEAD);
RedirectAnnotation(newMessH);
bodyOffset = CompBodyOffset(origMessH);
newBo = PETEGetTextLen(PETE,(*newMessH)->bodyPTE);
#ifdef DEBUG
if (BUG14) UpdateMyWindow(winWP);
#endif
err = PeteCopyNoLabel((*origMessH)->bodyPTE,(*newMessH)->bodyPTE,
bodyOffset,PETEGetTextLen(PETE,(*origMessH)->bodyPTE),newBo,nil,False,html?0:0xffffffff);
#ifdef DEBUG
if (BUG14) UpdateMyWindow(winWP);
#endif
style.tsLock = PrefIsSet(PREF_LOCK_REDIR) ? peModLock : peNoLock;
#ifdef DEBUG
if (BUG14) UpdateMyWindow(winWP);
#endif
PETESetTextStyle(PETE,(*newMessH)->bodyPTE,newBo,0x7fffffff,&style,peLockValid);
#ifdef DEBUG
if (BUG14) UpdateMyWindow(winWP);
#endif
if (err)
{
NoSaves = True; CloseMyWindow(newWinWP); NoSaves = False;
WarnUser(PETE_ERR,err);
PopPers();
return(nil);
}
if (SumOf(origMessH)->state!=SENT && SumOf(origMessH)->state!=UNSENT)
SumOf(newMessH)->sigId = SIG_NONE;
/*
* copy priority
*/
SumOf(newMessH)->origPriority = SumOf(newMessH)->priority =
SumOf(origMessH)->origPriority;
SumOf(newMessH)->outType = OUT_REDIRECT; // for statistics
/*
* state stuff
*/
AccuAddLong(&(*newMessH)->aSourceMID,SumOf(origMessH)->uidHash);
AccuAddLong(&(*newMessH)->aSourceMID,SumOf(origMessH)->state);
AccuAddLong(&(*newMessH)->aSourceMID,REDIST);
SetState((*origMessH)->tocH,(*origMessH)->sumNum,REDIST);
UpdateSum(newMessH,SumOf(newMessH)->offset,SumOf(newMessH)->length);
WeedXAttachments(newMessH,true);
if (MessFlagIsSet(origMessH,FLAG_HAS_ATT)) CopyAttachments(newMessH);
SpoolAttachments(newMessH);
#ifdef TWO
if (turbo && toWhom)
{
QueueMessage((*newMessH)->tocH,(*newMessH)->sumNum,PrefIsSet(PREF_AUTO_SEND)?kEuSendNow:kEuSendNext,0,true,false);
if (andDelete) DeleteMessage((*origMessH)->tocH,(*origMessH)->sumNum,False);
}
if (showIt && (!turbo || !toWhom))
#endif
{
ShowMyWindow(newWinWP);
newWin->isDirty = False;
PeteCleanList(newWin->pte);
}
}
PopPers();
return(newWin);
}
/**********************************************************************
* RedirectAnnotation - add proper annotation to redirect
**********************************************************************/
OSErr RedirectAnnotation(MessHandle messH)
{
Str255 scratch, who, orig;
OSErr err = noErr;
CompHeadGetStr(messH,FROM_HEAD,orig);
if (!IsMe(orig))
{
// Trim the trailing comment
UPtr spot = orig+*orig;
if (*spot==')')
{
while (spot>orig && *spot!='(' && *spot!='<') spot--;
if (spot>orig && *spot=='(') *orig = spot-orig-1;
}
// Now massage
if (*GetRealname(who))
{
ComposeRString(scratch,REDIST_ANNOTATE,who);
TrimWhite(orig);
PCat(orig,scratch);
err = SetMessText(messH,FROM_HEAD,orig+1,*orig);
}
}
return err;
}
/**********************************************************************
* CopyAttachments - copy attachments out of the message body and into
* the attachments line
**********************************************************************/
OSErr CopyAttachments(MessHandle messH)
{
long offset, absOffset;
UHandle text;
FSSpec attSpec;
UPtr spot;
HeadSpec hs;
if (!PeteGetTextAndSelection(TheBody,&text,nil,nil))
{
CompHeadFind(messH,0,&hs);
for (offset = 0;0<=(absOffset = FindAnAttachment(text,offset+hs.start,&attSpec,False,nil,nil,nil));)
{
offset = absOffset-hs.start-1;
for (spot=*text+absOffset;*spot!='\015' && spot<*text+hs.stop;spot++);
PETEInsertTextPtr(PETE,TheBody,absOffset,nil,(spot-*text)-absOffset,nil);
CompAttachSpec((*messH)->win,&attSpec);
CompHeadFind(messH,0,&hs);
}
for (offset = 0;0<=(absOffset = FindAnAttachment(text,offset+hs.start,&attSpec,True,nil,nil,nil));)
{
offset = absOffset-hs.start-1;
for (spot=*text+absOffset;spot<*text+hs.stop && *spot!='\015';spot++);
PETEInsertTextPtr(PETE,TheBody,absOffset,nil,(spot-*text)-absOffset,nil);
CompAttachSpec((*messH)->win,&attSpec);
CompHeadFind(messH,0,&hs);
}
}
return noErr; /* I have no idea what kinds of errors can happen here - mtc 11-18-03 */
}
/************************************************************************
* DoForwardMessage - forward a message to someone
************************************************************************/
MyWindowPtr DoForwardMessage(MyWindowPtr win,TextAddrHandle toWhom,Boolean showIt)
{
WindowPtr winWP = GetMyWindowWindowPtr (win);
MessHandle origMessH = (MessHandle)GetMyWindowPrivateData(win);
MessHandle newMessH;
MyWindowPtr newWin=nil;
WindowPtr newWinPtr;
Str255 scratch, subj;
long offset;
PETETextStyle style;
Boolean rich;
long origBo;
HeadSpec hs;
Boolean html = !PrefIsSet(PREF_SEND_ENRICHED_NEW);
NicknameWatcherFocusChange (win->pte); /* MJN */
if (GetWindowKind(winWP)!=COMP_WIN && !SaveMessHi(win,False)) return(nil);
#ifdef IMAP
// Make sure message has been downloaded if IMAP
if (!EnsureMsgDownloaded((*origMessH)->tocH,(*origMessH)->sumNum,true))
return nil;
#endif
PushPers(PERS_FORCE(MESS_TO_PERS(origMessH)));
if (newWin=DoComposeNew(toWhom))
{
newWinPtr = GetMyWindowWindowPtr (newWin);
#ifdef DEBUG
if (BUG14)
{
ShowMyWindow(newWinPtr);
UpdateMyWindow(newWinPtr);
}
#endif
newMessH = (MessHandle)GetMyWindowPrivateData(newWin);
rich = UseFlowOutExcerpt || (MessIsRich(origMessH) || MessIsRich(newMessH));
XferCustomTable(origMessH,newMessH);
if (SumOf(origMessH)->flags&FLAG_OUT)
{
BuildDateHeader(scratch,SumOf(origMessH)->seconds);
AppendMessText(newMessH,0,scratch+1,*scratch);
AppendMessText(newMessH,0,"\015",1);
}
/* handle the subject */
FindAndCopyHeader(origMessH,newMessH,HeaderName(SUBJ_HEAD),SUBJ_HEAD);
if (*GetRString(scratch,FWD_PREFIX))
{
TrimWhite(scratch);
CompHeadGetStr(newMessH,SUBJ_HEAD,subj);
if (!ReMatch(subj,scratch))
{
GetRString(scratch,FWD_PREFIX);
PrependMessText(newMessH,SUBJ_HEAD,scratch+1,*scratch);
}
}
/*
* stick it in
*/
offset = PETEGetTextLen(PETE,(*newMessH)->bodyPTE);
CompHeadFind(origMessH,0,&hs);
origBo = hs.value;
PeteCopy((*origMessH)->bodyPTE,(*newMessH)->bodyPTE,
0,origBo,offset,nil,True);
PETEInsertParaPtr(PETE,(*newMessH)->bodyPTE,kPETELastPara,nil,nil,0,nil); // make sure we have an empty para at end
PeteCopyNoLabel((*origMessH)->bodyPTE,(*newMessH)->bodyPTE,
origBo,PETEGetTextLen(PETE,(*origMessH)->bodyPTE),
PETEGetTextLen(PETE,(*newMessH)->bodyPTE),nil,False,html?0:0xffffffff);
#ifdef NEVER
PeteCopy((*origMessH)->bodyPTE,(*newMessH)->bodyPTE,
0,PETEGetTextLen(PETE,(*origMessH)->bodyPTE),
offset,nil,rich);
#endif
style.tsLock = 0;
PETESetTextStyle(PETE,(*newMessH)->bodyPTE,offset,0x7fffffff,&style,peLockValid);
if (rich && !(MainEvent.modifiers&optionKey) && *GetRString(scratch,FWD_QUOTE))
{
PeteExcerpt((*newMessH)->bodyPTE,offset,PETEGetTextLen(PETE,(*newMessH)->bodyPTE)-1);
if (PrefIsSet(PREF_SCHEERDER))
{
CompHeadFind(newMessH,0,&hs);
PeteSelect(nil,(*newMessH)->bodyPTE,hs.value,PeteLen((*newMessH)->bodyPTE));
PeteWrap((*newMessH)->win,(*newMessH)->bodyPTE,false);
}
}
/*
* make sure the last line is blank
*/
EnsureMessNewline(newMessH);
/*
* deal with attachments
*/
if (SumOf(origMessH)->flags&FLAG_OUT)
{
HeadSpec hs;
UHandle text = nil;
if (CompHeadFind(origMessH,ATTACH_HEAD,&hs) && !CompHeadGetText((*origMessH)->bodyPTE,&hs,&text) &&
CompHeadFind(newMessH,ATTACH_HEAD,&hs))
{
CompHeadSet((*newMessH)->bodyPTE,&hs,text);
}
ZapHandle(text);
}
else
{
if (MessFlagIsSet(origMessH,FLAG_HAS_ATT)) CopyAttachments(newMessH);
SpoolAttachments(newMessH);
}
/*
* quote them
*/
if (!rich && !(MainEvent.modifiers&optionKey))
QuoteLines((*newMessH)->bodyPTE,CompBodyOffset(newMessH)-1,0x7fffffff,FWD_QUOTE,nil);
if (GetWindowKind(winWP)!=COMP_WIN)
{
AccuAddLong(&(*newMessH)->aSourceMID,SumOf(origMessH)->uidHash);
AccuAddLong(&(*newMessH)->aSourceMID,SumOf(origMessH)->state);
AccuAddLong(&(*newMessH)->aSourceMID,FORWARDED);
SetState((*origMessH)->tocH,(*origMessH)->sumNum,FORWARDED);
}
/*
* Attributions
*/
Attribute(FWD_INTRO,origMessH,newMessH,False);
Attribute(FWD_TRAIL,origMessH,newMessH,True);
/*
* copy priority (not my idea)
*/
if (!PrefIsSet(PREF_NO_XF_PRIOR))
SumOf(newMessH)->origPriority = SumOf(newMessH)->priority =
SumOf(origMessH)->origPriority;
SumOf(newMessH)->outType = OUT_FORWARD; // for statistics
#ifdef TWO
ApplyDefaultStationery(newWin,True,True);
#endif
UpdateSum(newMessH,SumOf(newMessH)->offset,SumOf(newMessH)->length);
if (showIt)
{
ShowMyWindow(newWinPtr);
newWin->isDirty = False;
PeteCleanList(newWin->pte);
}
}
PopPers();
return(newWin);
}
/**********************************************************************
* CompBodyOffset - find the body in a comp message
**********************************************************************/
long CompBodyOffset(MessHandle messH)
{
HeadSpec hs;
if (CompHeadFind(messH,0,&hs)) return(hs.value);
else return(0);
}
/**********************************************************************
* DoFordirectMessage - forward or redirect a message, silently
**********************************************************************/
OSErr DoFordirectMessage(TOCHandle tocH,short sumNum,short flk,PStr addresses,Boolean bulk)
{
Boolean iOpened = !(*tocH)->sums[sumNum].messH;
MyWindowPtr origWin = GetAMessage(tocH,sumNum,nil,nil,False);
MessHandle newMessH;
OSErr err;
MyWindowPtr win;
//Enhanced Filters no such filter functionality in Light
if (!HasFeature (flk==flkForward ? featureFilterForward : featureFilterRedirect))
return (1);
UseFeature (flk==flkForward ? featureFilterForward : featureFilterRedirect);
if (!origWin) return(1);
win = flk==flkForward ? DoForwardMessage(origWin,0,False) :
DoRedistributeMessage(origWin,0,False,False,False);
if (iOpened) CloseMyWindow(GetMyWindowWindowPtr(origWin));
if (!win) return(1);
SetState(tocH,sumNum,flk==flkForward ? FORWARDED : REDIST);
newMessH = Win2MessH(win);
if (bulk) SetMessOpt(newMessH,OPT_BULK);
SetMessText(newMessH,TO_HEAD,addresses+1,*addresses);
if (err=QueueMessage((*newMessH)->tocH,(*newMessH)->sumNum,kEuSendNext,0,true,true))
DeleteMessage((*newMessH)->tocH,(*newMessH)->sumNum,False);
return(err);
}
/**********************************************************************
* DoReplyClosed - reply to a closed message, silently
**********************************************************************/
OSErr DoReplyClosed(TOCHandle tocH,short sumNum,Boolean all, Boolean self, Boolean quote, Boolean doFcc, short withWhich,Boolean vis,Boolean station)
{
Boolean iOpened = !(*tocH)->sums[sumNum].messH;
MyWindowPtr origWin = GetAMessage(tocH,sumNum,nil,nil,False);
MessHandle newMessH;
OSErr err;
MyWindowPtr win;
if (!origWin) return(1);
win = DoReplyMessage(origWin, all, self, quote, doFcc, withWhich,False,station,True);
if (iOpened) CloseMyWindow(GetMyWindowWindowPtr(origWin));
if (!win) return(1);
SetState(tocH,sumNum,REPLIED);
newMessH = Win2MessH(win);
SetMessOpt(newMessH,OPT_BULK);
if (err=QueueMessage((*newMessH)->tocH,(*newMessH)->sumNum,kEuSendNext,0,true,true))
DeleteMessage((*newMessH)->tocH,(*newMessH)->sumNum,False);
return(err);
}
/************************************************************************
* FindAndCopyHeader - pick the given header out of a message, and
* copy it to a composition message
************************************************************************/
int FindAndCopyHeader(MessHandle origMH,MessHandle newMH,UPtr fromHead,short toHead)
{
UPtr body;
long size;
int result;
Handle text;
PeteGetRawText((*origMH)->bodyPTE,&text);
body = LDRef(text);
size = BodyOffset(text);
result = TextFindAndCopyHeader(body,size,newMH,fromHead,toHead,0);
HUnlock(text);
return(result);
}
/************************************************************************
* CopyNewsgroups - copy newsgroups into new message
************************************************************************/
OSErr CopyNewsgroups(MessHandle origMH,MessHandle newMH)
{
Str255 s;
HeadSpec hs, origHS;
UHandle text=nil;
UHandle addresses = nil;
OSErr err = fnfErr;
UPtr address;
Boolean first = true;
GetRString(s,NEWSGROUPS);
if (CompHeadFindStr(origMH,s,&origHS))
if (!(err=CompHeadGetText((*origMH)->bodyPTE,&origHS,&text)))
if (!(err=SuckAddresses(&addresses,text,false,false,false,nil)) && addresses && *addresses && **addresses)
{
if (CompHeadFind(newMH,BCC_HEAD,&hs))
{
if (*GetRString(s,MAIL2NEWS))
{
if (hs.value!=hs.stop) InsertCommaIfNeedBe((*newMH)->bodyPTE,&hs);
CompHeadAppendPtr((*newMH)->bodyPTE,&hs,s+1,*s);
first = false;
}
for(address=LDRef(addresses);*address;address+=*address+2)
{
if (!first || hs.value!=hs.stop)
if (!InsertCommaIfNeedBe((*newMH)->bodyPTE,&hs))
CompHeadAppendPtr((*newMH)->bodyPTE,&hs," ",1);
if (err=CompHeadAppendPtr((*newMH)->bodyPTE,&hs,"<EFBFBD>",1)) break;
if (err=CompHeadAppendPtr((*newMH)->bodyPTE,&hs,address+1,*address)) break;
first = false;
}
}
}
return(err);
}
/************************************************************************
* TextFindAndCopyHeader - pick the given header out of a text block, and
* copy it to a composition message
************************************************************************/
int TextFindAndCopyHeader(UPtr body,long size,MessHandle newMH,UPtr fromHead,short toHead,short label)
{
MyWindowPtr newWin = (*newMH)->win;
UPtr bodyEnd = body+size;
UPtr spot;
Boolean first = True;
HeadSpec hs;
long labStart;
if ((body = FindHeaderString(body,fromHead,&size,False)) && size)
{
if (CompHeadFind(newMH,toHead,&hs))
{
labStart = hs.stop;
for(;;)
{
if (!first || hs.value!=hs.stop)
if (!InsertCommaIfNeedBe((*newMH)->bodyPTE,&hs))
CompHeadAppendPtr((*newMH)->bodyPTE,&hs," ",1);
CompHeadAppendPtr((*newMH)->bodyPTE,&hs,body,size);
first = False;
body += size;
while (IsWhite(*body)&&body<bodyEnd) body++; /* skip to newline */
body++; /* skip newline */
if (body<bodyEnd && IsWhite(*body)) /* continuation */
{
while (IsWhite(*body)&&body<bodyEnd) body++;
for (spot=body;spot<bodyEnd&&*spot!='\015';spot++);
do {spot--;} while (IsWhite(*spot));
size = spot-body+1;
if (!size) break;
}
else
break;
}
if (label && CompHeadFind(newMH,toHead,&hs) && hs.stop>labStart)
PeteLabel((*newMH)->bodyPTE,labStart,hs.stop,label,label);
}
}
return(body!=nil);
}
/************************************************************************
* XferCustomTable - transfer a custom table from a message to its reply
* (or forward or redirect)
************************************************************************/
void XferCustomTable(MessHandle origMessH,MessHandle newMessH)
{
short origTable;
Boolean isOut;
/*
* under the old regime, we don't do this step
*/
if (!NewTables) return;
origTable = SumOf(origMessH)->tableId;
/*
* if the original message had the default table, give the default table
* to the new message (which has already been done, so return)
*/
if (origTable == DEFAULT_TABLE) return;
/*
* if no table was on the original mail, use the default table
* this is different from non-MIME mail
*/
if (!origTable) return;
/*
* if original was outgoing, use same table
*/
isOut = (SumOf(origMessH)->flags&FLAG_OUT)!=0;
if (isOut) SumOf(newMessH)->tableId = origTable;
/*
* Otherwise, look for an out table that corresponds to the In table
* and use that.
*/
else if (GetResource_('taBL',origTable+1))
SumOf(newMessH)->tableId = origTable+1;
}
/************************************************************************
* WeedXAttachments - figure out if all the attachments are there
************************************************************************/
void WeedXAttachments(MessHandle messH,Boolean errReport)
{
short i;
FSSpec spec;
short err;
short removed = 0;
Boolean foundAny = False;
HeadSpec hs;
for (i=1;1!=(err=GetIndAttachment(messH,i,&spec,nil));i++)
{
if (err)
{
/* attachment does not exist */
removed++;
RemoveIndAttachment(messH,i);
i--;
}
else foundAny = True;
}
if (!foundAny && CompHeadFind(messH,ATTACH_HEAD,&hs) && hs.value!=hs.stop)
{
CompHeadSetPtr(TheBody,&hs,"",0);
removed = True;
}
if (removed && errReport) WarnUser(ATTACH_REMOVED,removed);
}
/**********************************************************************
* SpoolAttachments - copy attachments to the spool area
**********************************************************************/
OSErr SpoolAttachments(MessHandle messH)
{
short n;
short i;
OSErr err = noErr;
FSSpec spec;
/*
* count the attachments
*/
for (n=0;!GetIndAttachment(messH,n+1,&spec,nil);n++);
for (i=1;i<=n;i++) if (err=SpoolIndAttachment(messH,1)) break;
return(err);
}
/**********************************************************************
* SpoolIndAttachment - spool a single attachment
**********************************************************************/
OSErr SpoolIndAttachment(MessHandle messH,short i)
{
FSSpec spec, newSpec;
OSErr err = noErr;
/*
* make the folder
*/
if (err = MakeAttSubFolder(messH,SumOf(messH)->uidHash,&newSpec))
return(FileSystemError(COPY_ATTACHMENT,newSpec.name,err));
/*
* grab it
*/
if (!GetIndAttachment(messH,i,&spec,nil))
{
/*
* Copy the attachment
*/
PCopy(newSpec.name,spec.name);
if (!SameSpec(&spec,&newSpec) && !FSpIsItAFolder(&spec))
{
Str255 longName;
if ((err=FSpDupFile(&newSpec,&spec,False,False)))
return(FileSystemError(COPY_ATTACHMENT,spec.name,err));
// handle long filename
if (!FSpGetLongName(&spec,kTextEncodingUnknown,longName) && *longName>31)
FSpSetLongName(&newSpec,kTextEncodingUnknown,longName,&newSpec);
CompAttachSpec((*messH)->win,&newSpec);
RemoveIndAttachment(messH,i);
}
}
return(err);
}
/**********************************************************************
* MakeAttSubFolder - make the subfolder for attachment spooling
**********************************************************************/
OSErr MakeAttSubFolder(MessHandle messH,uLong uidHash,FSSpecPtr folder)
{
OSErr err;
FSSpec spool;
Str255 scratch;
long dirID;
if (messH && !MessOptIsSet(messH,OPT_HAS_SPOOL)) SetMessOpt(messH,OPT_HAS_SPOOL);
GetRString(folder->name,SPOOL_FOLDER); // in case of error
/*
* find the folder
*/
if (err=SubFolderSpec(SPOOL_FOLDER,&spool))
return(err);
/*
* message id
*/
NumToString(uidHash,scratch);
/*
* specify
*/
SimpleMakeFSSpec(spool.vRefNum,spool.parID,scratch,folder);
/*
* create
*/
err = FSpDirCreate(folder,smSystemScript,&dirID);
if (err==dupFNErr)
{
dirID = SpecDirId(folder);
if (dirID) err = noErr;
}
if (err) return(err);
/*
* and make it point into the folder
*/
folder->parID = dirID;
*folder->name = 0;
return(err);
}
/************************************************************************
* RemoveIndAttachment - remove a particular attachment
************************************************************************/
void RemoveIndAttachment(MessHandle messH, short index)
{
HeadSpec where;
FSSpec spec;
OSErr err = GetIndAttachment(messH,index,&spec,&where);
if (err!=1)
{
CompDelAttachment(messH,&where);
AttachSelect(messH);
}
}
/************************************************************************
* CopyToOut - copy a message to the Out mailbox
************************************************************************/
OSErr CopyToOut(TOCHandle fromTocH,short sumNum,TOCHandle toTocH)
{
MessHandle messH;
short err = 1;
MyWindowPtr win=nil, newWin=nil;
MSumType newSum,oldSum;
if (!(win = GetAMessage(fromTocH,sumNum,nil,nil,False))) return(1);
messH = Win2MessH(win);
fromTocH = (*messH)->tocH;
sumNum = (*messH)->sumNum;
if (newWin = DoSalvageMessage((*messH)->win,True))
{
if (SaveComp(newWin))
{
newSum = (*toTocH)->sums[(*toTocH)->count-1];
oldSum = (*fromTocH)->sums[sumNum];
SumInfoCpy(&newSum,&oldSum);
newSum.flags |= FLAG_OUT;
(*toTocH)->sums[(*toTocH)->count-1] = newSum;
err = noErr;
}
NoSaves = True; CloseMyWindow(GetMyWindowWindowPtr(newWin)); NoSaves = False;
}
if (win) CloseMyWindow(GetMyWindowWindowPtr(win));
return(err);
}
/************************************************************************
* SumInfoCpy - copy non-essential summary fields
************************************************************************/
void SumInfoCpy(MSumPtr newSum, MSumPtr oldSum)
{
newSum->state = oldSum->state;
PCopy(newSum->from,oldSum->from);
PCopy(newSum->subj,oldSum->subj);
newSum->seconds = oldSum->seconds;
newSum->flags = oldSum->flags;
newSum->tableId = oldSum->tableId;
newSum->sigId = SigValidate(oldSum->sigId);
newSum->priority = oldSum->priority;
newSum->origPriority = oldSum->origPriority;
BMD(&oldSum->spareShort2,&newSum->spareShort2,sizeof(newSum->spareShort2));
if (oldSum->opts&OPT_INLINE_SIG) newSum->opts |= OPT_INLINE_SIG;
}
/* The following hashing algorithm, KRHash, is derived from Karp & Rabin,
Harvard Center for Research in Computing Technology Tech Report TR-31-81. */
/* The prime number in use is KRHashPrime. It happens to be
the largest prime number that will fit in 31 bits, except for 2^31-1 itself. */
#define KRHashPrime (2147483629)
/************************************************************************
* HashWithSeed - generate a hash from a string and a seed.
************************************************************************/
uLong HashWithSeedLo(UPtr s, uLong n, uLong seed)
{
uLong sum = seed-1;
int Bit;
for (;n;n--,s++) {
for (Bit = 0x80; Bit != 0; Bit >>= 1) {
sum += sum;
if (sum >= KRHashPrime) sum -= KRHashPrime;
if ((*s) & Bit) ++sum;
if (sum >= KRHashPrime) sum -= KRHashPrime;
}
}
return(sum+1);
}
/************************************************************************
* MIDHash - hash a message id, stripping <>'s first
************************************************************************/
uLong MIDHash(UPtr text,long size)
{
Str255 scratch;
UHandle addresses;
SuckPtrAddresses(&addresses,text,size,False,False,False,nil);
if (addresses)
{
PCopy(scratch,*addresses);
ZapHandle(addresses);
return(Hash(scratch));
}
else return(kNoMessageId);
}
/************************************************************************
* SetHash - set a message's hash function
************************************************************************/
void SetHashLo(TOCHandle tocH,short sumNum,uLong hash,Boolean soft)
{
DBNoteUIDHash((*tocH)->sums[sumNum].uidHash,hash);
if (!soft || !ValidHash((*tocH)->sums[sumNum].uidHash))
(*tocH)->sums[sumNum].uidHash = hash;
if (!soft || !ValidHash((*tocH)->sums[sumNum].msgIdHash))
(*tocH)->sums[sumNum].msgIdHash = hash;
TOCSetDirty(tocH,true);
}
/************************************************************************
* Rehash - recompute the hash for a message
************************************************************************/
void RehashLo(TOCHandle tocH,short sumNum,UHandle text,Boolean soft)
{
Str255 scratch;
UPtr spot;
long size = GetHandleSize_(text);
uLong hash;
spot = LDRef(text);
GetRString(scratch,HEADER_STRN+MSGID_HEAD);
if (spot = FindHeaderString(spot,scratch,&size,False))
hash = MIDHash(spot,size);
else
{
NewMessageId(scratch);
hash = MIDHash(scratch+1,*scratch);
}
SetHashLo(tocH,sumNum,hash,soft);
UL(text);
}
#define PREVIEW_ID_MULT_REDO (-3)
#define PREVIEW_ID_MULT (-2)
/************************************************************************
* Preview - preview a message, if desired
************************************************************************/
void Preview(TOCHandle tocH,short sumNum)
{
WindowPtr tocWinWP = GetMyWindowWindowPtr ((*tocH)->win);
MessHandle messH;
long id;
PETEHandle pte;
MyWindowPtr messWin = nil;
Boolean active = false;
short ezOpenSum;
OSErr err;
Str63 profileName;
#ifdef IMAP
short oldPreview;
#endif
if (!(pte = (*tocH)->previewPTE)) return;
messH = (*tocH)->sums[sumNum].messH;
if (sumNum>=0)
{
EnsureMID(tocH,sumNum);
id = (*tocH)->sums[sumNum].serialNum;
}
else if (sumNum==-2)
{
if (!(*tocH)->conConMultiScan) id = (*tocH)->previewID;
else if (ConConMultipleAppropriate(tocH))
{
(*tocH)->previewID = PREVIEW_ID_MULT_REDO;
id = PREVIEW_ID_MULT;
}
else
id = 0;
}
else
id = 0;
if ((*tocH)->previewID==id)
{
PeteCalcOn((*tocH)->previewPTE);
if (id && (*tocH)->lastSameTicks!=1 && id!=PREVIEW_ID_MULT) // we have a message and we're not already done
{
if (!InBG // front only
&& IsWindowVisible(tocWinWP) // visible
&& tocWinWP==FrontWindow_() // frontmost
&& (*tocH)->userActive) // user has recently clicked or typed at me
{
if (TickCount()-(*tocH)->lastSameTicks>10 // it's been a while since we last checked
&& PreviewReadTimer // the user wants this marking
&& TickCount()-(*tocH)->lastSameTicks>GetRLong(PREVIEW_READ_SECS)*60) // and it's been long enough
{
(*tocH)->lastSameTicks = 1;
#ifdef IMAP
// do not automatically mark IMAP minimal headers as read, ever.
if (!(*tocH)->imapTOC || IMAPMessageDownloaded(tocH,sumNum))
#endif
BeenThereDoneThat(tocH,sumNum);
}
}
else
(*tocH)->lastSameTicks = TickCount(); // reset counter if user not active
}
return;
}
else (*tocH)->lastSameTicks = TickCount();
#ifdef IMAP
if ((*tocH)->previewID && (*tocH)->imapTOC)
{
// Cancel the IMAP download if this is an imap message ...
FindRealSummary(tocH, (*tocH)->previewID, &oldPreview);
IMAPAbortMessageFetch(tocH, oldPreview);
}
#endif
(*tocH)->previewID = id;
(*tocH)->ezOpenSerialNum = 0;
PeteSetTextPtr(pte," ",1); // put something into it so that all the plains work
PetePlain(pte,0,1,peAllValid);
PetePlainParaAt(pte,0,1);
PeteLock(pte,0,1,peModLock|peSelectLock);
PeteSetTextPtr(pte,"",0); // clear it now
if (id && id!=PREVIEW_ID_MULT)
{
SelectBoxRange(tocH,sumNum,sumNum,false,-1,-1);
// need to open?
if (!messH)
{
short realSumNum;
TOCHandle realTocH;
if (!(realTocH = GetRealTOC(tocH,sumNum,&realSumNum)))
return;
if (!(messH = (*realTocH)->sums[realSumNum].messH))
{
messWin = (*realTocH)->which==OUT ? OpenComp(realTocH,realSumNum,nil,nil,false,false) :
#ifdef IMAP
OpenMessage(realTocH,realSumNum,nil,nil,false,true);
#else
OpenMessage(realTocH,realSumNum,nil,nil,false);
#endif
if (messWin) messH = Win2MessH(messWin);
}
#ifdef IMAP
//
// fire off a thread to fetch the next message as well, if it's not already there.
// ... but don't do this for the search window.
//
if (!Offline && AutoCheckOK() && !((*tocH)->virtualTOC))
{
short sumToOpenNext;
// open the next summary according to EZOpen
sumToOpenNext = EzOpenFind(realTocH,realSumNum);
if (sumToOpenNext >= 0)
{
if (!IMAPMessageDownloaded(realTocH, sumToOpenNext) && !IMAPMessageBeingDownloaded(realTocH, sumToOpenNext))
UIDDownloadMessage(realTocH, (*realTocH)->sums[sumToOpenNext].uidHash,false, false);
}
}
#endif
}
if (messH)
{
Boolean junk = SumOf(messH)->spamScore >= GetRLong(JUNK_MAILBOX_THRESHHOLD);
(*PeteExtra(pte))->containsJunkMail = junk;
PeteCalcOff(pte);
PETEAllowUndo(PETE,pte,false,true);
if (!ConConMess(messH,pte,BoxPreviewProfile(profileName,tocH,CONCON_PREVIEW_PROFILE),nil,nil))
{
// concentrator did it!
// lock all the text
PeteLock(pte,0,PeteLen(pte),peModLock);
PeteCalcOn(pte);
}
else
{
long body = SumOf(messH)->bodyOffset - (*messH)->weeded;
long len;
long scanned;
UHandle text;
long oldID;
long para;
PETEParaInfo pinfo;
long bite;
// no drawing, please
PeteCalcOff(pte);
// Make sure we examine it for funny business
HiliteOddReply(messH);
// figure out what we need to copy
bite = len = PETEGetTextLen(PETE,TheBody);
// find a paragraph that includes 3K of text
if (bite>3 K) bite = 3 K;
PeteParaConvert(TheBody,bite-100,bite+100);
para = PeteParaAt(TheBody,bite-1);
Zero(pinfo);
PETEGetParaInfo(PETE,TheBody,para,&pinfo);
bite = pinfo.paraOffset+pinfo.paraLength;
// Reset the copy mask to include the reply-to label for now
PETESetLabelCopyMask(PETE,LABEL_COPY_MASK|pReplyLabel);
PeteGetTextAndSelection(TheBody,&text,nil,nil);
if (bite==len) while (bite && (*text)[bite-1]=='\015' || IsWhite((*text)[bite-1])) bite--;
// do the copy, but fool the graphics into thinking it's the same Pete record
oldID = (*PeteExtra(pte))->id;
(*PeteExtra(pte))->id = (*PeteExtra(TheBody))->id;
PeteCopy(TheBody,pte,0,bite,0,nil,false);
(*PeteExtra(pte))->id = oldID;
// pre-scan for some url's and attachments
PeteSetURLRescan(pte,0);
for (scanned=(*PeteExtra(pte))->urlScanned;
scanned!=-1 && scanned<3 K;
scanned=(*PeteExtra(pte))->urlScanned)
PeteURLScan((*tocH)->win,pte);
for (scanned=(*PeteExtra(pte))->quoteScanned;
scanned!=-1 && scanned<3 K;
scanned=(*PeteExtra(pte))->quoteScanned)
PeteQuoteScan(pte);
#ifdef ATT_ICONS
// Note: code assumes preview shows same headers as message
if (MessFlagIsSet(messH,FLAG_OUT))
{
FSSpec spec;
short i;
HeadSpec hs;
for (i=1;1!=GetIndAttachment(messH,i,&spec,&hs);i++)
PeteFileGraphicRange(pte,hs.start,hs.stop,&spec,fgAttachment);
}
#endif
// scroll to proper spot
PeteSelect(nil,pte,body+1,body+1);
PETESetCallback(PETE,pte,(void*)PeteCancelOnEvents,peProgressLoop);
if (err=PeteCalcOn(pte))
{
(*tocH)->previewID = 0;
PeteSetTextPtr(pte,"",0);
}
PETESetCallback(PETE,pte,(void*)PeteBusy,peProgressLoop);
if (!err)
{
ShowMessageSeparator(pte,((*tocH)->previewHi/(*tocH)->win->vPitch)>GetRLong(PREVIEW_HEADER_HIDE));
// Now copy the remainder
if (bite<len)
{
PeteCalcOff(pte);
PeteGetTextAndSelection(TheBody,&text,nil,nil);
PeteSetURLRescan(pte,3 K - 255);
while (len && (*text)[len-1]=='\015') len--;
if (bite<len)
{
(*PeteExtra(pte))->id = (*PeteExtra(TheBody))->id;
PeteCopy(TheBody,pte,bite,len,PETEGetTextLen(PETE,pte),nil,false);
(*PeteExtra(pte))->id = oldID;
PETESetCallback(PETE,pte,(void*)PeteCancelOnEvents,peProgressLoop);
if (PeteCalcOn(pte))
{
(*tocH)->previewID = 0;
PeteSetTextPtr(pte,"",0);
}
PETESetCallback(PETE,pte,(void*)PeteBusy,peProgressLoop);
}
}
}
// Reset the copy mask to include the reply-to label for now
PETESetLabelCopyMask(PETE,LABEL_COPY_MASK);
// And lock all the text
PeteLock(pte,0,PeteLen(pte),peModLock);
}
}
if (messWin) CloseMyWindow(GetMyWindowWindowPtr(messWin));
if (!PrefIsSet(PREF_NO_EZ_OPEN))
{
ezOpenSum = EzOpenFind(tocH,sumNum);
if (ezOpenSum>=0)
{
(*tocH)->ezOpenSerialNum = (*tocH)->sums[ezOpenSum].serialNum;
CacheMessage(tocH,ezOpenSum);
}
}
if (AnalSpeakPhrases())
{
(*PeteExtra(pte))->taeDesired = true;
(*PeteExtra(pte))->taeSpeak = true;
}
}
else if (id==PREVIEW_ID_MULT)
{
ConConMultiple(tocH,pte,BoxPreviewProfile(profileName,tocH,CONCON_MULTI_PREVIEW_PROFILE),conConOutSeparatorRule,nil,nil);
(*tocH)->previewID = id;
}
else
BoxPreviewProfile(nil,tocH,0);
PeteActivate(pte,(*tocH)->win->isActive && !(*tocH)->listFocus,false);
}
/************************************************************************
* HTMLifyText - insert appropriate BR's, process and remove "related:" lines, etc.
************************************************************************/
void HTMLifyText(MyWindowPtr win,Handle text)
{
Accumulator a;
Ptr spot,end,lastSpot;
long len;
Str32 sBR;
char lastChar;
long offset = 0;
PartDesc pd;
StackHandle stack;
// Insert <BR> after each header line
if (!AccuInit(&a))
{
GetRString(sBR,HTMLTagsStrn+htmlBR);
len = 0;
spot = LDRef(text);
lastChar = 0;
lastSpot = spot;
for(end = spot + GetHandleSize(text); spot < end; spot++)
{
if (*spot=='\r')
{
AccuAddPtr(&a,lastSpot,spot-lastSpot);
AccuAddChar(&a,'<'); // Add <BR>
AccuAddStr(&a,sBR);
AccuAddChar(&a,'>');
if (lastChar == '\r')
break; // Hit 2 newlines in a row. End of headers.
lastSpot = spot+1;
}
lastChar = *spot;
}
UL(text);
Munger(text,0,nil,spot-*text,LDRef(a.data),a.offset);
offset = a.offset;
AccuZap(a);
}
// Build parts stack from "related:" lines and remove them
StackInit(sizeof(PartDesc),&stack);
while (0<=(offset = FindAnAttachment(text,offset,&pd.spec,false,&pd.cid,&pd.relURL,&pd.absURL)))
{
Ptr lineEnd,end;
long len;
StackQueue(&pd,stack);
// Remove related line
end = *text + GetHandleSize_(text);
for (lineEnd=*text+offset;lineEnd<end && *lineEnd!='\015';lineEnd++);
len = lineEnd-*text;
if (lineEnd<end) len++; // Get past CR
Munger(text,offset,nil,len,end,nil); // Delete the line
}
(*PeteExtra(win->pte))->partStack = stack;
}
#ifdef IMAP
/************************************************************************
* EnsureMsgDownloaded - if IMAP message, make sure it is downloaded
************************************************************************/
Boolean EnsureMsgDownloaded(TOCHandle tocH,short sumNum,Boolean attachmentsToo)
{
// Actually go fetch this message if we must
if ((*tocH)->imapTOC)
{
short n;
Boolean result;
// make sure this message has already been downloaded ...
if (!IMAPMessageDownloaded(tocH, sumNum))
{
if (IMAPMessageBeingDownloaded(tocH,sumNum))
{
// This message is currently being downloaded. Wait for it to finish
while (IMAPMessageBeingDownloaded(tocH,sumNum))
{
CycleBalls();
if (MyYieldToAnyThread())
break;
if (CommandPeriod) return (false);
}
}
else
{
// Download this message in the foreground
if (UIDDownloadMessage(tocH, (*tocH)->sums[sumNum].uidHash, true, false)!=noErr)
return false;
}
}
// put the message in the mailbox
for(n=100;n && !IMAPMessageDownloaded(tocH,sumNum);n--)
// call a reasonable number of times to get the job done
// there may be other messages that also need to be processed
UpdateIMAPMailbox(tocH);
// did we get the message ok?
if ((result=IMAPMessageDownloaded(tocH,sumNum)) && attachmentsToo)
{
// go fetch all the attachments for this message. Wait for them.
if (!FetchAllIMAPAttachments(tocH, sumNum, true))
result = false;
}
return result;
}
return true;
}
#endif
/************************************************************************
* EnableMsgButtons - enable or disable message buttons
************************************************************************/
void EnableMsgButtons(MyWindowPtr win,Boolean enable)
{
MControlsEnum cIndex;
ControlHandle ctl;
for (cIndex=mcWrite;cIndex<=mcTrash;cIndex++)
{
if (ctl=FindControlByRefCon(win,cIndex))
{
if (enable) ActivateControl(ctl);
else DeactivateControl(ctl);
}
}
}
/************************************************************************
* GetMessageLength - return length of message
************************************************************************/
uLong GetMessageLength(TOCHandle tocH,short sumNum)
{
short realSum;
TOCHandle realTOC;
return (realTOC = GetRealTOC(tocH,sumNum,&realSum)) ? (*realTOC)->sums[realSum].length : 0;
}
/************************************************************************
* RedateTS - redate a message
************************************************************************/
OSErr RedateTS(TOCHandle tocH,short sumNum)
{
Str255 dateStr;
uLong secs;
uLong zoneSecs;
OSErr err = CacheMessage(tocH,sumNum);
if (!err && (*tocH)->sums[sumNum].cache)
{
HNoPurge((*tocH)->sums[sumNum].cache);
HandleHeadGetPStr((*tocH)->sums[sumNum].cache,HeaderStrn+DATE_HEAD,dateStr);
if (!*dateStr) return fnfErr;
secs = BeautifyDate(dateStr,&zoneSecs);
TimeStamp(tocH,sumNum,secs,zoneSecs);
HPurge((*tocH)->sums[sumNum].cache);
}
return err;
}
/************************************************************************
* CurAddr - extract the current address from a window, if we have one
************************************************************************/
PStr CurAddr(MyWindowPtr win,PStr addr)
{
if (win->curAddr) return win->curAddr(win,addr);
else return CurAddrSel(win,addr);
}
/************************************************************************
* CurAddrSel - extract the current address from the selection, if we have one
************************************************************************/
PStr CurAddrSel(MyWindowPtr win,PStr addr)
{
if (win->pte && *PeteSelectedString(addr,win->pte) && *ShortAddr(addr,addr)) return addr;
return nil;
}