<> <> <> <> DIRECTORY GVAnswer USING [MakeHeader], GVBasics USING [ItemType, RName], GVMailParse, GVNames, GVSend USING [AddRecipient, AddToItem, CheckValidity, Close, Create, Handle, Send, SendFailed, StartItem, StartSend, StartSendInfo], List USING [Append], InputFocus USING [GetInputFocus], Menus USING [MouseButton], IO, PeanutRetrieve USING [CopyMessages], Rope, TextNode, TiogaOps, ViewerEvents, ViewerOps, ViewerTools, ViewerClasses, UserCredentials USING [GetUserCredentials], UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc, Token], PeanutSendMail, PeanutWindow, PeanutParse; PeanutSendMailImpl: CEDAR MONITOR IMPORTS GVAnswer, GVMailParse, GVNames, GVSend, List, IO, PeanutRetrieve, Rope, TiogaOps, ViewerTools, TextNode, UserCredentials, UserProfile, PeanutWindow, ViewerEvents, ViewerOps EXPORTS PeanutSendMail, PeanutParse SHARES Rope = BEGIN OPEN PeanutSendMail, PeanutParse; <> TiogaCTRL: GVBasics.ItemType; 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 defaultRegistry: PUBLIC ROPE_ "pa"; arpaGatewayHosts: PUBLIC LIST OF ROPE_ LIST["PARC-MAXC", "PARC", "MAXC"]; 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 _ [ ["Reply-To", simpleRope], -- this is really wrong, a special case for now ["Sender", simpleRope], ["From", simpleRope], ["To", rNameList], ["cc", rNameList], ["c", rNameList], ["bcc", rNameList], ["Date", simpleRope], ["Subject", simpleRope], ["Categories", rCatList], ["In-Reply-To", simpleRope] ]; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> AuthenticateUser: PUBLIC PROC RETURNS [BOOL] = BEGIN uN: ROPE_ UserCredentials.GetUserCredentials[].name; proc: PROC[r: ROPE] = {InternalReport[r]}; pos: INT; auth: GVNames.AuthenticateInfo; IF Rope.Length[uN] = 0 THEN {proc["Please Login"]; RETURN[FALSE]}; <> IF userRName = NIL THEN IF (pos_Rope.SkipTo[s: uN, skip: "."]) = Rope.Length[uN] THEN {simpleUserName_ uN; userRName_ Rope.Cat[uN, ".", defaultRegistry]} ELSE {simpleUserName_ Rope.Substr[uN, 0, pos]; userRName_ uN}; SELECT auth_ GVNames.Authenticate[userRName, UserCredentials.GetUserCredentials[].password] FROM group => proc["... Can't login as group"]; individual => {needToAuthenticate_ FALSE; RETURN[TRUE]}; notFound => {proc[uN]; 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[AnswerGetChar, txt.Length[], IF UserProfile.Boolean[key:"Peanut.CarbonCopyToSelf",default:TRUE] THEN simpleUserName ELSE "", defaultRegistry]; 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[simpleUserName]; 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: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS[abort: BOOL _ FALSE]>> = { 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 UserProfile.Boolean["Peanut.CarbonCopyToSelf",TRUE] 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] }; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> SendMsg: PUBLIC PROC [mouseButton: Menus.MouseButton, shift, control: BOOL] = BEGIN senderV: Viewer; fullSender, 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; <> IF NOT Rope.Equal[UserProfile.Token[key: "Peanut.OutGoingMailFile", default:""],""] THEN PeanutRetrieve.CopyMessages[to: UserProfile.Token[key: "Peanut.OutGoingMailFile", default:""], 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 }; IF smr.arpaRecipient THEN fullSender_ Rope.Concat[simpleUserName, "@Parc"] ELSE fullSender_ userRName; specialTxt_ Rope.Cat["Date: ", IO.PutFR[NIL, IO.time[]], IF smr.from = NIL THEN "\nFrom: " ELSE "\nSender: ", fullSender, "\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; }; ]; IF sendHandle#NIL THEN GVSend.Close[sendHandle]; }; 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.GetUserCredentials[].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[".", defaultRegistry] 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]]]}; 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 => IF i = fromF THEN msg.from_ GetFieldBody[pH, GetNextMsgChar] ELSE {IF i = replyToF THEN msg.replyTo_ TRUE; []_ 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; TRUSTED { TiogaCTRL _ LOOPHOLE[1013B] }; UserProfile.CallWhenProfileChanges[PeanutSendInit]; END.