DIRECTORY BasicTime USING [GMT, Now, nullGMT, Unpack, Unpacked], GVAnswer USING [MakeHeader], GVBasics USING [ItemType, RName], GVMailParse USING [endOfInput, FinalizeParse, GetFieldBody, GetFieldName, InitializeParse, ParseError, ParseHandle, ParseNameList], GVNames USING [Authenticate, AuthenticateInfo], GVSend USING [AddRecipient, AddToItem, CheckValidity, Create, Handle, Send, SendFailed, StartItem, StartSend, StartSendInfo], InputFocus USING [GetInputFocus], IO USING [int, PutFR, rope], List USING [Append], Menus USING [MouseButton], PeanutParse USING [MessageFieldIndex, MessageInfo], PeanutProfile USING [ccToSelf, outgoingMailFile], PeanutRetrieve USING [CopyMessages], PeanutSendMail USING [SendingRec, SendMsgRecObject, SendParseStatus], PeanutWindow USING [abortFlag, dirtyMailMessageIcon, mailMessageIcon, OutputRope], Rope USING [Cat, Concat, Equal, Fetch, Find, Length, ROPE, RopeRep, Size, Substr], TextNode USING [Location, LocRelative, NarrowToTextNode, Ref, RefTextNode], TiogaOps USING [BackSpace, CallWithLocks, CommandProc, DeleteNextCharacter, FirstChild, GetRope, GetSelection, InsertRope, LastChild, LastLocWithin, Location, Lock, LockSel, LocRelative, NextPlaceholder, Parent, Ref, RegisterCommand, Root, SelectBranches, SelectionGrain, SelectPoint, SetSelection, ToPrimary, Unlock, UnlockSel, ViewerDoc], UserCredentials USING [Get], UserProfile USING [CallWhenProfileChanges, ProfileChangedProc], ViewerClasses USING [Lock, Viewer], ViewerEvents USING [RegisterEventProc, ViewerEvent], ViewerOps USING [CloseViewer, DestroyViewer, OpenIcon, PaintViewer], ViewerTools USING [EnableUserEdits, GetSelectedViewer, GetTiogaContents, InhibitUserEdits, MakeNewTextViewer, SelPosRec, SetContents, SetSelection, TiogaContents]; PeanutSendMailImpl: CEDAR MONITOR IMPORTS BasicTime, GVAnswer, GVMailParse, GVNames, GVSend, List, IO, PeanutProfile, PeanutRetrieve, Rope, TiogaOps, ViewerTools, TextNode, UserCredentials, UserProfile, PeanutWindow, ViewerEvents, ViewerOps EXPORTS PeanutSendMail, PeanutParse SHARES Rope = BEGIN OPEN PeanutSendMail, PeanutParse; TiogaCTRL: GVBasics.ItemType = LOOPHOLE[1013B]; Viewer: TYPE = ViewerClasses.Viewer; ROPE: TYPE = Rope.ROPE; RName: TYPE = GVBasics.RName; userRName: PUBLIC ROPE_ NIL; -- user name with registry simpleUserName: PUBLIC ROPE_ NIL; -- user name without registry userRegistry: PUBLIC ROPE_ NIL; defaultRegistry: PUBLIC ROPE_ "pa"; needToAuthenticate: BOOL_ TRUE; subTocc: ROPE_ "Subject: \001Topic\002\nTo: \001Recipients\002\ncc: \001Copies To\002"; messageRope: ROPE_ "\n\n\001Message\002\n"; answRope: ROPE_ "\n\n\001Message\002\n"; fwdRope: ROPE_ "\n\n\001CoveringMessage\002\n\n-------------------------------------\n"; fwdRope2: ROPE_ "\n------------------------------------------------------------\n"; messageParseArray: PUBLIC ARRAY MessageFieldIndex OF MessageInfo _ [ replyToF: ["Reply-To", simpleRope], -- this is really wrong, a special case for now senderF: ["Sender", simpleRope], fromF: ["From", simpleRope], toF: ["To", rNameList], ccF: ["cc", rNameList], cF: ["c", rNameList], bccF: ["bcc", rNameList], dateF: ["Date", simpleRope], subjectF: ["Subject", simpleRope], categoriesF: ["Categories", rCatList], inReplyToF: ["In-Reply-To", simpleRope], voiceF: ["VoiceFileID", simpleRope] ]; AuthenticateUser: PUBLIC PROC RETURNS [BOOL] = BEGIN uN: ROPE ~ UserCredentials.Get[].name; proc: PROC[r: ROPE] = {InternalReport[r]}; auth: GVNames.AuthenticateInfo; IF Rope.Length[uN] = 0 THEN { proc["Please Login"]; RETURN[FALSE] } ELSE { dot: INT ~ uN.Find["."]; IF dot<0 THEN { simpleUserName _ uN; userRegistry _ defaultRegistry; userRName _ Rope.Cat[simpleUserName, ".", userRegistry]; } ELSE { simpleUserName _ uN.Substr[len: dot]; userRegistry _ uN.Substr[start: dot+1]; userRName _ uN; }; }; auth _ GVNames.Authenticate[userRName, UserCredentials.Get[].password]; SELECT auth FROM group => proc["... Can't login as group"]; individual => {needToAuthenticate _ FALSE; RETURN[TRUE]}; notFound => {proc[userRName]; proc[" is invalid - please Login"]}; allDown => proc["... No server responded"]; badPwd => proc["... Your Password is invalid - please Login"]; ENDCASE; RETURN[FALSE]; END; PeanutSendInit: UserProfile.ProfileChangedProc = { needToAuthenticate_ TRUE }; ObjectRope: TYPE = object node Rope.RopeRep; CreateMessageRope: PUBLIC PROC [firstMessageNode: TextNode.Ref] RETURNS [r: Rope.ROPE] = { RETURN [NEW[ObjectRope _ [node[object[2048,firstMessageNode,FetchFromMessageRope,NIL,NIL]]]]] }; FetchFromMessageRope: PROC [data: REF, index: INT] RETURNS [CHAR] = { firstMessageNode: TextNode.Ref _ NARROW[data]; loc: TextNode.Location = TextNode.LocRelative[[firstMessageNode,0], index]; n: TextNode.RefTextNode = TextNode.NarrowToTextNode[loc.node]; IF n=NIL THEN RETURN[0C]; IF loc.where >= Rope.Size[n.rope] THEN RETURN ['\n]; RETURN [Rope.Fetch[n.rope, loc.where]] }; TopParent: PROC [node, root: TiogaOps.Ref _ NIL] RETURNS [parent: TiogaOps.Ref] = { IF node=NIL THEN RETURN [NIL]; IF root=NIL THEN root _ TiogaOps.Root[node]; DO parent _ TiogaOps.Parent[node]; IF parent=root THEN RETURN [node]; node _ parent; ENDLOOP }; AnswerMsg: PUBLIC PROC [mouseButton: Menus.MouseButton, shift, control: BOOL] = BEGIN notOk: BOOL; errorIndex: INT; txt, answer: ROPE; messageNode: TiogaOps.Ref; AnswerGetChar: PROC[pos: INT] RETURNS[CHAR] = {RETURN[txt.Fetch[pos]]}; IF needToAuthenticate AND ~AuthenticateUser[] THEN RETURN; messageNode _ TopParent[TiogaOps.GetSelection[].start.node]; IF messageNode = NIL THEN { InternalReport["\nSelect message to be answered."]; RETURN }; TRUSTED {txt _ CreateMessageRope[LOOPHOLE[TiogaOps.FirstChild[messageNode]]] }; [notOk, answer, errorIndex]_ GVAnswer.MakeHeader[ getChar: AnswerGetChar, getLength: txt.Length[], userName: IF PeanutProfile.ccToSelf THEN simpleUserName ELSE "", userRegistry: userRegistry ]; IF notOk THEN { InternalReport[IO.PutFR[ "\nSyntax error in line previous to line containing pos %g (in message being answered)", IO.int[errorIndex]]]; RETURN }; NewForm[Rope.Cat[answer, answRope]]; END; ForwardMsg: PUBLIC PROC [mouseButton: Menus.MouseButton, shift, control: BOOL] = { sourceViewer: Viewer; lockedPrimary, lockedSecondary, lockedDest, lockedSource: BOOL _ FALSE; start, end: TiogaOps.Location; sourceDoc, destDoc, messageHeader: TiogaOps.Ref; singleNode: BOOL; Simple: PROC [node: TiogaOps.Ref] RETURNS [BOOL] = { n: TextNode.RefTextNode; TRUSTED { n _ TextNode.NarrowToTextNode[LOOPHOLE[node]] }; IF n=NIL THEN RETURN [FALSE]; singleNode _ n.child=NIL AND n.last; IF ~singleNode THEN RETURN [FALSE]; RETURN [n.runs=NIL] }; Cleanup: PROC = { IF lockedPrimary THEN TiogaOps.UnlockSel[primary]; IF lockedSecondary THEN TiogaOps.UnlockSel[secondary]; IF lockedDest THEN TiogaOps.Unlock[destDoc]; IF lockedSource THEN TiogaOps.Unlock[sourceDoc]; }; IF needToAuthenticate AND ~AuthenticateUser[] THEN RETURN; TiogaOps.LockSel[primary]; lockedPrimary _ TRUE; [sourceViewer, start, end, ----, ----, ----] _ TiogaOps.GetSelection[]; IF sourceViewer = NIL THEN { TiogaOps.UnlockSel[primary]; InternalReport["\nSelect message to be forwarded."]; RETURN }; IF start.node=end.node AND Simple[start.node] THEN -- send without Tioga formatting NewForm[Rope.Cat["Subject: \001Topic\002\nTo: \001Recipients\002\nReply-To: ", simpleUserName, "\ncc: \001Copies To\002", fwdRope, TiogaOps.GetRope[start.node], fwdRope2]] ELSE { -- forward with Tioga formatting OPEN TiogaOps; destViewer: Viewer; sourceDoc _ ViewerDoc[sourceViewer]; messageHeader _ TopParent[start.node, sourceDoc]; IF TopParent[end.node, sourceDoc] # messageHeader THEN { UnlockSel[primary]; InternalReport["\nSelect single message to be forwarded."]; RETURN }; destViewer _ ViewerTools.MakeNewTextViewer[info: [name: "Message", icon: PeanutWindow.mailMessageIcon, iconic: FALSE]]; [] _ ViewerEvents.RegisterEventProc[proc: MailMessageHasBeenEdited, event: edit, filter: destViewer, before: TRUE]; [] _ ViewerEvents.RegisterEventProc[proc: MailMessageHasBeenSaved, event: save, filter: destViewer, before: FALSE]; destDoc _ ViewerDoc[destViewer]; LockSel[secondary]; lockedSecondary _ TRUE; Lock[destDoc]; lockedDest _ TRUE; IF sourceDoc#destDoc THEN { Lock[sourceDoc]; lockedSource _ TRUE }; SelectPoint[destViewer, [FirstChild[destDoc],0], primary]; InsertRope["Subject: \001Topic\002\nTo: \001Recipients\002\nReply-To: "]; InsertRope[userRName]; InsertRope["\ncc: \001Copies To\002"]; InsertRope[fwdRope]; IF ~singleNode THEN BackSpace[]; -- get rid of CR at end SelectBranches[ -- source viewer: sourceViewer, level: IF singleNode THEN char ELSE branch, caretBefore: FALSE, pendingDelete: FALSE, which: secondary, start: FirstChild[messageHeader], end: LastChild[messageHeader]]; ToPrimary[]; SelectPoint[destViewer, [FirstChild[destDoc],0], primary]; [] _ NextPlaceholder[gotoend: TRUE]; }; Cleanup[]; }; MailMessageHasBeenEdited: PROC [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS[abort: BOOL _ FALSE] = { IF before THEN { viewer.icon _ PeanutWindow.dirtyMailMessageIcon; IF viewer.iconic THEN ViewerOps.PaintViewer[viewer: viewer, hint: all]; }; }; MailMessageHasBeenSaved: PROC [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS[abort: BOOL _ FALSE] = { IF NOT before AND viewer.file # NIL THEN { viewer.icon _ PeanutWindow.mailMessageIcon; IF viewer.iconic THEN ViewerOps.PaintViewer[viewer: viewer, hint: all]; }; }; MailMessageHasBeenReset: TiogaOps.CommandProc = { viewer.icon _ PeanutWindow.mailMessageIcon; IF viewer.iconic THEN ViewerOps.PaintViewer[viewer: viewer, hint: all]; }; NewMsgForm: PUBLIC PROC [mouseButton: Menus.MouseButton, shift, control: BOOL] = { NewForm[Rope.Cat[ "Subject: \001Topic\002\nTo: \001Recipients\002\nReply-To: ", simpleUserName, "\ncc: \001Copies To\002", IF PeanutProfile.ccToSelf THEN Rope.Concat[", " , simpleUserName] ELSE "", messageRope]]}; NewForm: PROC [r: ROPE] = { newForm: Viewer; ViewerTools.SetContents[ newForm _ ViewerTools.MakeNewTextViewer[info: [name: "Message", icon: PeanutWindow.mailMessageIcon, iconic: FALSE]], r]; [] _ ViewerEvents.RegisterEventProc[proc: MailMessageHasBeenEdited, event: edit, filter: newForm, before: TRUE]; [] _ ViewerEvents.RegisterEventProc[proc: MailMessageHasBeenSaved, event: save, filter: newForm, before: FALSE]; TiogaOps.RegisterCommand[name: $reset, proc: MailMessageHasBeenReset, before: FALSE]; ViewerTools.SetSelection[newForm, NEW[ViewerTools.SelPosRec_ [0, 0]]]; newForm.class.notify[newForm, LIST[$NextPlaceholder]]; }; InternalReport: PROC [r: ROPE] = INLINE { PeanutWindow.OutputRope[r] }; CheckForAbort: PROC RETURNS [BOOL] = INLINE { RETURN [PeanutWindow.abortFlag] }; RFC822Date: PROC[gmt: BasicTime.GMT _ BasicTime.nullGMT] RETURNS[date: ROPE] = BEGIN OPEN IO; upt: BasicTime.Unpacked _ BasicTime.Unpack[IF gmt = BasicTime.nullGMT THEN BasicTime.Now[] ELSE gmt]; zone: ROPE; month, tyme, year: ROPE; timeFormat: ROPE = "%02g:%02g:%02g %g"; -- "hh:mm:ss zzz" dateFormat: ROPE = "%2g %g %g %g"; -- "dd mmm yy timeFormat" arpaNeg: BOOL_ upt.zone > 0; aZone: INT_ ABS[upt.zone]; zDif: INT_ aZone / 60; zMul: INT_ zDif * 60; IF (zMul = aZone) AND arpaNeg THEN { IF upt.dst = yes THEN SELECT zDif FROM 0 => zone_ "UT"; 4 => zone_ "EDT"; 5 => zone_ "CDT"; 6 => zone_ "MDT"; 8 => zone_ "PDT"; -- fix zone bug (wso) ENDCASE ELSE SELECT zDif FROM 0 => zone_ "UT"; 5 => zone_ "EST"; 6 => zone_ "CST"; 7 => zone_ "MST"; 8 => zone_ "PST"; ENDCASE; }; IF zone = NIL THEN { mm: INT_ aZone - zMul; zone_ PutFR[IF arpaNeg THEN "-%02g%02g" ELSE "+%02g%02g", int[zDif], int[mm]]; }; SELECT upt.month FROM January => month_ "Jan"; February => month_ "Feb"; March => month_ "Mar"; April => month_ "Apr"; May => month_ "May"; June => month_ "Jun"; July => month_ "Jul"; August => month_ "Aug"; September => month_ "Sep"; October => month_ "Oct"; November => month_ "Nov"; December => month_ "Dec"; unspecified => ERROR; ENDCASE => ERROR; year_ Rope.Substr[PutFR[NIL, int[upt.year]], 2]; tyme_ PutFR[timeFormat, int[upt.hour], int[upt.minute], int[upt.second], rope[zone]]; date_ PutFR[dateFormat, int[upt.day], rope[month], rope[year], rope[tyme]]; END; SendMsg: PUBLIC PROC [mouseButton: Menus.MouseButton, shift, control: BOOL] = BEGIN senderV: Viewer; oldName: ROPE; restore: BOOL _ FALSE; Restore: PROC = { senderV.name _ oldName; ViewerTools.EnableUserEdits[senderV]; ViewerOps.OpenIcon[senderV] }; BEGIN ENABLE UNWIND => { IF restore THEN Restore[] }; status: SendParseStatus; sPos, mPos: INT; specialTxt, formatting: ROPE; smr: SendingRec; contents: ViewerTools.TiogaContents; { outgoing: ROPE = PeanutProfile.outgoingMailFile; IF Rope.Length[outgoing]>0 THEN PeanutRetrieve.CopyMessages[to: outgoing, delete: FALSE]; }; IF needToAuthenticate AND ~AuthenticateUser[] THEN RETURN; PeanutWindow.abortFlag _ FALSE; senderV _ ViewerTools.GetSelectedViewer[]; IF senderV = NIL THEN { InternalReport["\nSelect message to be sent."]; RETURN }; oldName _ senderV.name; senderV.name _ "Sending"; restore _ TRUE; ViewerOps.CloseViewer[senderV]; TRUSTED {smr_ NEW[SendMsgRecObject_ [fullText: CreateMessageRope[LOOPHOLE[TiogaOps.ViewerDoc[senderV]]]]] }; InternalReport["\nParsing..."]; [status, sPos, mPos]_ ParseTextToBeSent[smr]; IF (status # ok) AND (status # includesPublicDL) THEN BEGIN SELECT status FROM fieldNotAllowed => IF sPos # mPos THEN { InternalReport[Rope.Substr[smr.fullText, sPos, mPos-sPos-1]]; InternalReport[" field is not allowed."]} ELSE InternalReport[IO.PutFR[" field at pos %g is not allowed", IO.int[sPos]]]; syntaxError => IF sPos # mPos THEN { InternalReport["\nSyntax error on line beginning with "]; InternalReport[Rope.Substr[smr.fullText, sPos, mPos-sPos-1]]} ELSE InternalReport[IO.PutFR["..... Syntax error at position %g ", IO.int[sPos]]]; includesPrivateDL => InternalReport[" Private dl's are not yet implemented"]; ENDCASE => ERROR; Restore; RETURN; END; IF CheckForAbort[] THEN { Restore; InternalReport["... Message NOT sent."]; RETURN }; specialTxt_ Rope.Cat["Date: ", RFC822Date[], IF smr.from = NIL THEN "\nFrom: " ELSE "\nSender: ", userRName, "\n"]; InsertIntoSender[senderV, specialTxt, 0]; contents_ ViewerTools.GetTiogaContents[senderV]; IF (formatting_ contents.formatting).Length[] = 0 THEN smr.fullText_ contents.contents ELSE { -- check for null at end of contents; move it to formatting last: INT_ contents.contents.Length[] - 1; IF contents.contents.Fetch[last] = '\000 THEN { -- NULL for padding smr.fullText_ Rope.Substr[contents.contents, 1, last-1]; formatting_ Rope.Concat["\000", contents.formatting] } ELSE smr.fullText_ Rope.Substr[contents.contents, 1] }; InternalReport["... Sending message..."]; IF Send[smr, formatting] THEN { InternalReport[" ... Message has been delivered"]; ViewerOps.DestroyViewer[senderV] } ELSE { DeleteFromSender[senderV, specialTxt, 0]; Restore; InternalReport["... Message NOT sent."] }; END; END; DeleteFromSender: PROC[v: Viewer, what: ROPE, where: INT] = BEGIN OPEN TiogaOps; thisV: Ref_ ViewerDoc[v]; DeleteChars: PROC[root: Ref] = BEGIN startLoc: Location; prevV: Viewer; prevStart, prevEnd: Location; prevLevel: SelectionGrain; cb, pd: BOOL; startLoc_ LocRelative[[FirstChild[thisV], 0], where]; [prevV, prevStart, prevEnd, prevLevel, cb, pd]_ GetSelection[primary]; ViewerTools.EnableUserEdits[v]; SelectPoint[v, startLoc, primary]; DeleteNextCharacter[Rope.Size[what]]; ViewerTools.InhibitUserEdits[v]; IF (prevV # v) AND (prevV#NIL) THEN SetSelection[prevV, prevStart, prevEnd, prevLevel, cb, pd]; END; CallWithLocks[DeleteChars, thisV]; END; InsertIntoSender: PROC[v: Viewer, what: ROPE, where: INT] = BEGIN OPEN TiogaOps; thisV: Ref_ ViewerDoc[v]; InsertChars: PROC[root: Ref] = BEGIN insertLoc: Location; prevV: Viewer; prevStart, prevEnd: Location; prevLevel: SelectionGrain; cb, pd: BOOL; IF where < 0 THEN insertLoc_ LastLocWithin[LastChild[thisV]] ELSE insertLoc_ LocRelative[[FirstChild[thisV], 0], where]; [prevV, prevStart, prevEnd, prevLevel, cb, pd]_ GetSelection[primary]; ViewerTools.EnableUserEdits[v]; SelectPoint[v, insertLoc, primary]; InsertRope[what]; ViewerTools.InhibitUserEdits[v]; IF (prevV # v) AND (prevV#NIL) THEN SetSelection[prevV, prevStart, prevEnd, prevLevel, cb, pd]; END; CallWithLocks[InsertChars, thisV]; END; Send: PROC[smr: SendingRec, formatting: ROPE] RETURNS[ sent: BOOLEAN] = { sendHandle: GVSend.Handle _ GVSend.Create[]; sent _ SendMessage[smr, formatting, sendHandle, TRUE ! GVSend.SendFailed => IF notDelivered THEN { InternalReport["\nCommunication failure during send."]; sent _ FALSE; CONTINUE } ELSE { InternalReport["\nCommunication failure, but message delivered."]; sent _ TRUE; CONTINUE; }; ]; }; SendMessage: PROC[smr: SendingRec, formatting: ROPE, h: GVSend.Handle, validateFlag: BOOL] RETURNS[ sent: BOOLEAN ] = { DO ENABLE GVSend.SendFailed => CONTINUE; -- try again if SendFailed startInfo: GVSend.StartSendInfo; stepper: LIST OF RName; numRecips: INT _ 0; numValidRecips: INT; firstInvalidUser: BOOL_ TRUE; numInvalidUsers: INTEGER_ 0; ReportFromSend: PROC[r: ROPE] = { InternalReport[r]}; InvalidUserProc: PROC [ userNum: INT, userName: RName ] = { IF firstInvalidUser THEN {ReportFromSend["\nInvalid user(s): "]; firstInvalidUser_ FALSE}; SELECT numInvalidUsers _ numInvalidUsers + 1 FROM 1 => ReportFromSend[userName]; IN [2..5] => {ReportFromSend[", "]; ReportFromSend[userName]}; 6 => ReportFromSend[", ..."]; ENDCASE; } ; sent _ FALSE ; startInfo _ GVSend.StartSend[ handle: h, senderPwd: UserCredentials.Get[].password, sender: userRName, returnTo: userRName, validate: validateFlag ] ; SELECT startInfo FROM badPwd => {ReportFromSend["\nInvalid password"]; RETURN}; badSender => {ReportFromSend["\nInvalid sender name"]; RETURN}; badReturnTo => {ReportFromSend["\nBad return-to field"]; RETURN}; allDown => {ReportFromSend["\nAll servers are down"]; RETURN}; ok => { stepper _ smr.to; WHILE stepper # NIL DO GVSend.AddRecipient[ h, stepper.first ]; numRecips _ numRecips + 1; stepper _ stepper.rest; ENDLOOP; IF CheckForAbort[] THEN RETURN; stepper _ smr.cc; WHILE stepper # NIL DO GVSend.AddRecipient[ h, stepper.first ] ; numRecips _ numRecips + 1 ; stepper _ stepper.rest ; ENDLOOP ; IF CheckForAbort[] THEN RETURN; IF validateFlag THEN { IF (numValidRecips_ GVSend.CheckValidity[ h, InvalidUserProc]) = 0 THEN { ReportFromSend["\nThere were NO valid recipients."]; RETURN }; IF numValidRecips # numRecips THEN { tempInteger: INT _ numRecips-numValidRecips; ReportFromSend[IO.PutFR["\nThere were %g invalid recipients,", IO.int[tempInteger] ]]; RETURN; }; }; IF CheckForAbort[] THEN RETURN; ReportFromSend[IO.PutFR["..sending to %g recipients", IO.int[numValidRecips]]]; validateFlag_ FALSE; -- if sending fails, don't need to re-validate GVSend.StartItem[h, Text]; GVSend.AddToItem[h, smr.fullText]; IF formatting#NIL THEN { -- send the formatting info as a second item GVSend.StartItem[h, TiogaCTRL]; GVSend.AddToItem[h, formatting] }; IF CheckForAbort[] THEN RETURN; GVSend.Send[ h ] ; sent_ TRUE; RETURN; } ; ENDCASE ; ENDLOOP; } ; CanonicalName: PUBLIC PROC [simpleName, registry: ROPE] RETURNS[name: ROPE] = BEGIN name_ simpleName; IF registry.Length[] = 0 THEN name_ name.Cat[".", userRegistry] ELSE name_ name.Cat[".", registry]; END; ParseTextToBeSent: PROC[msg: SendingRec] RETURNS[status: SendParseStatus, sPos, mPos: INT] = BEGIN OPEN GVMailParse; mLF: MessageInfo; tHeaders: LIST OF ROPE_ NIL; msgText: ROPE_ msg.fullText; lastCharPos: INT_ msgText.Length[] - 1; lastCharIsCR: BOOL_ (msgText.Fetch[lastCharPos] = '\n); countOfRecipients, dlCount: INT_ 0; GetNextMsgChar: PROC[] RETURNS [ch: CHAR] = { IF mPos <= lastCharPos THEN ch_ Rope.Fetch[msgText, mPos] ELSE IF (mPos=lastCharPos+1) AND ~lastCharIsCR THEN ch_ '\n ELSE ch_ endOfInput; mPos_ mPos + 1; }; RNameListField: PROC[index: MessageFieldIndex] = BEGIN fieldBody, fbEnd: LIST OF RName_ NIL; AnotherRName: PROC[r1, r2: ROPE, isFile, isNested: BOOL] RETURNS [ROPE, BOOLEAN] = BEGIN name: ROPE; name_ CanonicalName[r1, r2]; IF fbEnd=NIL THEN fbEnd_ fieldBody_ CONS[name, NIL] ELSE fbEnd_ fbEnd.rest_ CONS[name, NIL]; IF isFile THEN status_ includesPrivateDL ELSE IF name.Find["^"] < 0 THEN countOfRecipients_ countOfRecipients + 1 ELSE { IF status # includesPrivateDL THEN status_ includesPublicDL; dlCount_ dlCount + 1 }; RETURN[NIL, FALSE]; END; ParseNameList[pH, GetNextMsgChar, AnotherRName, NIL]; SELECT index FROM toF => IF msg.to = NIL THEN msg.to_ fieldBody ELSE IF fieldBody#NIL THEN TRUSTED {msg.to_ LOOPHOLE[List.Append[LOOPHOLE[msg.to], LOOPHOLE[fieldBody]]]}; ccF, cF, bccF => IF msg.cc = NIL THEN msg.cc_ fieldBody ELSE IF fieldBody#NIL THEN TRUSTED {msg.cc_ LOOPHOLE[List.Append[LOOPHOLE[msg.cc], LOOPHOLE[fieldBody]]]}; fromF => msg.from_ fieldBody.first; -- needs to be non-NIL ENDCASE => ERROR; END; pH: ParseHandle; field: ROPE_ NIL; fieldNotRecognized: BOOL; mPos_ 0; -- where we are in the fulltext, for parsing status_ ok; -- start with good status pH_ InitializeParse[]; DO sPos_ mPos; [field, fieldNotRecognized]_ GetFieldName[pH, GetNextMsgChar ! ParseError => { FinalizeParse[pH]; GOTO errorExit}]; IF ~fieldNotRecognized THEN EXIT; IF Rope.Equal[field, "Sender", FALSE] OR Rope.Equal[field, "Date", FALSE] THEN RETURN[fieldNotAllowed, sPos, mPos]; FOR i: MessageFieldIndex IN MessageFieldIndex DO { mLF_ messageParseArray[i]; IF Rope.Equal[messageParseArray[i].name, field, FALSE] THEN -- ignore case { fieldNotRecognized_ FALSE; SELECT mLF.fType FROM simpleRope => SELECT i FROM fromF => RNameListField[i ! ParseError => GOTO errorExit]; replyToF => {msg.replyTo_ TRUE; []_ GetFieldBody[pH, GetNextMsgChar, TRUE]}; subjectF => msg.subject_ GetFieldBody[pH, GetNextMsgChar]; voiceF => msg.voiceID_ GetFieldBody[pH, GetNextMsgChar]; ENDCASE => []_ GetFieldBody[pH, GetNextMsgChar, TRUE]; rCatList => []_ GetFieldBody[pH, GetNextMsgChar, TRUE]; rNameList => RNameListField[i ! ParseError => GOTO errorExit]; ENDCASE => ERROR; EXIT }; }; ENDLOOP; IF fieldNotRecognized THEN []_ GetFieldBody[pH, GetNextMsgChar]; -- skip anything not recognized ENDLOOP; FinalizeParse[pH]; msg.endHeadersPos_ mPos - 1; msg.numRecipients_ countOfRecipients; msg.numDLs_ dlCount; EXITS errorExit => RETURN[syntaxError, sPos, mPos]; END; UserProfile.CallWhenProfileChanges[PeanutSendInit]; END. bPeanutSendMailImpl.mesa Created by Paxton, April 1, 1983 3:02 pm Last Edited by: Pausch, July 18, 1983 2:13 pm Last Edited by: Willie-Sue, May 2, 1984 10:30:22 am PDT Last Edited by: Doug Wyatt, May 24, 1984 6:33:13 pm PDT Global types & variables * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * This probably should be revised to work if selection is in the message header rather than the message body. [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS[abort: BOOL _ FALSE] * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * generates arpa standard time, dd mmm yy hh:mm:ss zzz Swiped from WalnutSendMailImpl of November 10, 1983 this needs to be done first, before the user can change the selection out from under me ... the "right" way would be to access the sending viewer later and make tioga lower level calls, but CopyMessages is easier to use, so I just call it before the selection can change. NOTE, however that if the message has troubles being sent, this copy will still work ... * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * IF sendHandle#NIL THEN GVSend.Close[sendHandle]; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * now we are positioned at the beginning of the body of the message if any recipient is at another arpa site, all recipients should be arpa qualified however, like Laurel, we'll only do the From/Sender field in full arpa regalia ÊC˜šœ™J™(J™-J™7J™7—J™šÏk ˜ Jšœ œœ"˜6Jšœ œ˜Jšœ œ˜!Jšœ œr˜ƒJšœœ"˜/Jšœœq˜}Jšœ œ˜!Jšœœ˜Jšœœ ˜Jšœœ˜Jšœ œ"˜3Jšœœ˜1Jšœœ˜$Jšœœ1˜EJšœ œ@˜RJšœœ+œ˜RJšœ œ=˜KJšœ œÆ˜ÔJšœœ˜Jšœ œ.˜?Jšœœ˜#Jšœ œ"˜4Jšœ œ5˜DJšœ œ’˜£J˜—Jšœœ˜!Jšœ:œ‹˜ÎJšœ˜#Jšœ˜ Jšœœ˜'J˜Jšœ™J˜Jšœœ˜/Jšœœ˜$Jšœœœ˜Jšœœ˜J˜Jšœ œœœÏc˜9Jšœœœœž˜@Jšœœœœ˜J˜Jšœœœ˜#J˜Jšœœœ˜J˜šœ œ˜J˜H—Jšœ œ˜+Jšœ œ˜(Jšœ œK˜XJšœ œE˜SJ˜šœœœœ˜DJšœ%ž/˜TJ˜ J˜J˜J˜J˜J˜J˜J˜"J˜&J˜(Jšœ#˜#˜J˜——JšœU™Uš Ïnœœœœœ˜.Jš˜Jšœœ˜&Jšœœœ˜*J˜Jšœœœœ˜Cšœ˜Jšœœ˜šœœ˜Jšœ˜Jšœ˜Jšœ8˜8Jšœ˜—šœ˜Jšœ%˜%Jšœ'˜'Jšœ˜Jšœ˜—Jšœ˜J˜—JšœG˜Gšœ˜J˜*Jšœ$œœœ˜9JšœB˜BJ˜+J˜>Jšœ˜—J˜Jšœœ˜Jšœ˜J˜—JšœU™UJ˜šœGœ˜NJ˜—Jšœ œ˜,J˜š Ÿœœœ"œ œ˜Zšœœ ˜Jšœ8œœ˜G——J˜š Ÿœœœ œœœ˜EJšœ!œ˜.JšœK˜KJ˜>Jšœœœœ˜Jšœ œœ˜4Jšœ#˜)J˜—šŸ œœœœ˜SJš œœœœœ˜Jšœœœ˜,š˜J˜Jšœ œœ˜"J˜Jšœ˜ J˜——šŸ œœœ2œ˜OJš˜Jšœœ˜ Jšœ œ˜Jšœ œ˜Jšœ˜Jš Ÿ œœœœœœ˜GJ˜Jšœœœœ˜:Jšœ<˜<šœœœ˜Jšœ4œ˜=—Jšœœ&˜OJ˜šœ1˜1Jšœ0˜0Jšœ œœœ˜@Jšœ˜Jšœ˜J˜—šœœ˜šœœ˜JšœX˜XJšœ˜—Jšœ˜ J˜—Jšœ$˜$Jšœ˜J˜—šŸ œœœ2œ˜RJšœ˜Jšœ:œœ˜GJ˜Jšœ0˜0Jšœ œ˜J˜J™J™kJ™J™šŸœœœœ˜4J˜Jšœ!œ ˜:Jš œœœœœ˜Jšœœœ˜$Jšœ œœœ˜#Jšœ œ˜—šŸœœ˜Jšœœ˜2Jšœœ˜6Jšœ œ˜,Jšœœ˜0J˜—J˜Jšœœœœ˜:Jšœ+œ˜0Jšœžœžœžœ˜Gšœœœ˜JšœRœ˜[—šœœœž ˜SJšœ¬˜¬—šœž ˜'Jšœ ˜Jšœ˜Jšœ$˜$Jšœ1˜1šœ0œ˜8JšœPœ˜Y—Jšœoœ˜wJšœmœ˜sJšœlœ˜sJšœ ˜ Jšœ&œ˜+Jšœœ˜!Jšœœ#œ˜CJšœ:˜:JšœI˜IJšœ˜Jšœ&˜&Jšœ˜Jšœ œž˜9šœž ˜Jš œœ œœœ˜UJšœœ˜'JšœA˜A—J˜ Jšœ:˜:Jšœœ˜$J˜—J˜ J˜—š ŸœœIœœœœ˜šœœ˜Jšœ0˜0Jšœœ5˜J—J˜—J˜š ŸœœIœœœœ˜Œš œœœœœ˜*Jšœ+˜+Jšœœ5˜J—J˜—šŸœ˜.Jšœj™jšœ˜Jšœ+˜+Jšœœ3˜H—J˜—šŸ œœœ2œ˜Ršœ˜Jšœ=˜=Jšœ*˜*Jšœœ$œ˜KJšœ˜—J˜—šŸœœœ˜Jšœ˜šœ˜Jšœlœ˜x—Jšœjœ˜pJšœiœ˜pJ•StartOfExpansionC[name: ATOM, proc: TiogaOps.CommandProc, before: BOOL _ TRUE]šœNœ˜UJšœ"œ!˜FJšœœ˜9J˜—šŸœœœœ ˜GJ˜—š Ÿ œœœœœœ˜PJ˜—JšœU™UJ˜š Ÿ œœœœœ˜NJšœ4™4Jšœ3™3Jšœœœ˜˜Jšœœœœ˜K—Jšœœ˜ Jšœœ˜Jšœ œž˜:Jšœ œž˜>J˜Jšœ œ˜Jšœœœ ˜Jšœœ ˜Jšœœ ˜šœœ œ˜$šœœœ˜&J˜J˜J˜J˜Jšœž˜(Jš˜—šœœ˜J˜J˜J˜J˜J˜Jšœ˜—J˜J˜—šœœœ˜Jšœœ˜Jšœ œ œ œ"˜NJ˜J˜—šœ ˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜Jšœœ˜Jšœœ˜J˜—Jšœœ˜0J˜U˜KJ˜—Jšœ˜J˜—J˜šŸœœœ2œ˜MJš˜Jšœ˜Jšœ œ˜Jšœ œœ˜šŸœœ˜Jšœ˜Jšœ%˜%Jšœ˜—Jš˜Jšœœœ œ ˜/J˜Jšœ œ˜Jšœœ˜J˜J˜$J˜Jšœè™èšœ œ"˜2Jšœœ3œ˜YJ˜—J˜Jšœœœœ˜:Jšœœ˜J˜Jšœ*˜*Jšœ œœ3œ˜QJšœ˜Jšœ$œ˜)Jšœ˜J˜šœœ˜#Jšœœ#˜H—J˜J˜-J˜šœœœ˜;šœ˜˜šœ ˜šœ?˜?J˜)——Jšœœ+œ ˜P—˜šœ ˜˜;Jšœ=˜=——Jšœœ.œ ˜S—J˜MJšœœ˜—J˜Jšœ˜Jšœ˜J˜—šœœ˜Jšœ1˜1Jšœ˜ J˜—šœ,˜,Jšœ œœ œ ˜F—Jšœ)˜)Jšœ0˜0Jšœ0œ ˜Všœž;˜BJšœœ!˜*šœ'œž˜CJ˜8J˜6—Jšœ3˜7—J˜J˜)šœœ˜Jšœ2˜2J˜"—šœ˜Jšœ)˜)Jšœ˜Jšœ*˜*—J˜Jšœœ˜ J˜—šŸœœœ œ˜;Jšœœ ˜J˜šŸ œœ˜$Jšœ˜J˜J˜J˜Jšœœ˜ šœ5˜5J˜—J˜FJ˜Jšœ"˜"J˜%J˜ šœ œœ˜#J˜=—Jšœ˜—Jšœ"˜"Jšœ˜J˜—šŸœœœ œ˜;Jšœœ ˜J˜šŸ œœ˜$J˜J˜J˜J˜Jšœœ˜ Jšœ œ,˜=šœ7˜;J˜—J˜FJ˜J˜#J˜J˜ šœ œœ˜#J˜=—Jšœ˜—J˜"Jšœ˜J˜J˜—JšœO™Oš Ÿœœœœœ˜IJ˜,šœ0œ˜Kšœœ˜Jšœ7˜7Jšœœœ˜—šœ˜JšœB˜BJšœœœ˜—J˜—Jšœ œœ™0˜J˜——šŸ œœœ"œ˜ZJšœœ˜Jšœœœž˜DJ˜ Jšœ œœ˜Jšœ œ˜Jšœœ˜Jšœœœ˜Jšœœ˜J˜JšŸœœœ˜5šŸœœ œ˜;Jšœœ;œ˜Zšœ'˜1J˜Jšœ<˜>J˜—Jšœ˜J˜J˜—Jšœœ˜šœ(˜(J˜*J˜J˜J˜J˜—šœ ˜Jšœ1œ˜9Jšœ7œ˜NJšœ9œ˜AJšœ6œ˜>˜J˜šœ œœ˜J˜(J˜J˜Jšœ˜—Jšœœœ˜J˜šœ œœ˜J˜)J˜J˜Jšœ˜ —Jšœœœ˜šœœ˜šœAœ˜IJšœ5œ˜>—šœœ˜%Jšœ œ˜,Jšœœ.œ˜VJšœ˜J˜—J˜J˜—Jšœœœ˜Jšœœ%œ˜OJšœœž.˜DJ˜J˜"šœ œœž,˜EJšœB˜B—Jšœœœ˜J˜Jšœœ˜ Jšœ˜J˜—Jšœ˜ —Jšœ˜J˜—J˜JšœO™Oš Ÿ œœœœœœ˜Mš˜J˜šœœ"˜?Jšœ˜#——Jšœ˜J˜—šŸœœœ&œ˜\Jšœœ ˜J˜Jš œ œœœœ˜Jšœ œ˜Jšœ œ˜'Jšœœ%˜7Jšœœ˜#J˜šŸœœœœ˜-Jšœœ˜9šœœœœ˜;Jšœ˜—J˜J˜—J˜šŸœœ˜0Jš˜Jšœœœœ˜%šŸ œœ œœœœœ˜RJš˜Jšœœ˜ J˜š œœœœœ˜3Jšœœœ˜(—šœœ˜(šœœœ)˜Hš˜šœœœ˜>J˜—J˜———Jšœœœ˜šœ˜J˜——Jšœ0œ˜5šœ˜šœœ œœ˜-šœœ œ˜š˜Jšœ œ œ œ˜G———šœœ œœ˜7šœœ œ˜š˜Jšœ œ œ œ˜G———Jšœ%ž˜;Jšœœ˜—Jšœ˜J˜—J˜Jšœœœ˜Jšœœ˜Jšœ ž,˜6Jšœ ž˜&J˜J˜J˜š˜J˜ ˜LJšœœ ˜&—šœœœ˜!š œœœœ˜NJšœ˜$J˜—Jšœœ˜0˜šœ.œœž˜Jšœœ˜šœ ˜˜ šœ˜ Jšœ*œ ˜:šœœ˜Jšœ%œ˜,—J˜:J˜8—Jšœ)œ˜6—Jšœ1œ˜7Jšœ.œ ˜>Jšœœ˜Jš˜J˜———J˜—Jšœ˜šœ˜Jšœ&ž˜E——Jšœ˜J˜—JšœA™AJ˜J˜J˜JšœQ™QJšœN™NJ˜J˜&J˜š˜Jšœ œ˜-—Jšœ˜—J˜Jšœ3˜3J˜Jšœ˜—…—WBuç