/* 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 || sizepte,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,¤t.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;pro = !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 Ò%p,%pÓ Ò%pÓ->Ò%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(*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 Ò%p,%pÓ Ò%pÓ->Ò%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 ( spot24 && 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 (spotuidHash; 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Ò%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.ioDrMdDatwhich==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,"e); 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;sNumsums[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.valuebodyPTE,&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;spotbodyPTE); 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;offsetemoDesired = !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; texttocH,(*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 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,"©",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)&&bodylabStart) 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 (biteid = (*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
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
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;lineEndpte))->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; }