File: PeanutSendMailImpl.mesa
April 1, 1983 3:02 pm
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, Reverse],
InputFocus USING [GetInputFocus],
Menus USING [MouseButton],
IO,
Rope,
TextNode,
TiogaOps,
ViewerOps,
ViewerTools,
ViewerClasses,
UserExec USING [GetNameAndPassword],
UserProfile USING [CallWhenProfileChanges, ProfileChangedProc],
PeanutSendMail,
PeanutWindow,
PeanutParse;
PeanutSendMailImpl: CEDAR MONITOR
IMPORTS
GVAnswer, GVMailParse, GVNames, GVSend, List, IO, Rope, TiogaOps, ViewerTools, TextNode, UserExec, UserProfile, PeanutWindow, ViewerOps
EXPORTS PeanutSendMail, PeanutParse
SHARES Rope =
BEGIN OPEN PeanutSendMail, PeanutParse;
Global types & variables
TiogaCTRL: GVBasics.ItemType;
Viewer: TYPE = ViewerClasses.Viewer;
ROPE: TYPE = Rope.ROPE;
RName: TYPE = GVBasics.RName;
userRName: PUBLIC ROPENIL;   -- user name with registry
simpleUserName: PUBLIC ROPENIL;  -- user name without registry
defaultRegistry: PUBLIC ROPE← "pa";
arpaGatewayHosts: PUBLIC LIST OF ROPELIST["PARC-MAXC", "PARC", "MAXC"];
needToAuthenticate: BOOLTRUE;
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← UserExec.GetNameAndPassword[].name;
proc: PROC[r: ROPE] = {InternalReport[r]};
pos: INT;
auth: GVNames.AuthenticateInfo;
IF Rope.Length[uN] = 0 THEN {proc["Please Login"]; RETURN[FALSE]};
check if userRName is already set
IF userRName = NIL THEN IF 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, UserExec.GetNameAndPassword[].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 ERROR;
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;
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]← GVAnswer.MakeHeader[AnswerGetChar, txt.Length[],
simpleUserName, defaultRegistry, arpaGatewayHosts];
IF notOk THEN { InternalReport["\nFailed to parse message header."]; RETURN };
NewForm[Rope.Cat[answer, answRope]];
END;
ForwardMsg: PUBLIC PROC [mouseButton: Menus.MouseButton, shift, control: BOOL] = {
sourceViewer: Viewer;
lockedPrimary, lockedSecondary, lockedDest, lockedSource: BOOLFALSE;
start, end: TiogaOps.Location;
sourceDoc, destDoc, messageHeader: TiogaOps.Ref;
singleNode: BOOL;
This probably should be revised to work if selection is in the message header rather than the message body.
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", iconic: 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[];
};
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", ", ", simpleUserName,
messageRope]]};
NewForm: PROC [r: ROPE] = {
newForm: Viewer;
ViewerTools.SetContents[
newForm ← ViewerTools.MakeNewTextViewer[info: [name: "Message", iconic: FALSE]], r];
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
status: SendParseStatus;
sPos, mPos: INT;
specialTxt, formatting: ROPE;
fullSender, oldName: ROPE;
smr: SendingRec;
senderV: Viewer;
contents: ViewerTools.TiogaContents;
Restore: PROC = { senderV.name ← oldName; ViewerOps.OpenIcon[senderV] };
IF needToAuthenticate AND ~AuthenticateUser[] THEN RETURN;
senderV ← ViewerTools.GetSelectedViewer[];
IF senderV = NIL THEN { InternalReport["\nSelect message to be sent."]; RETURN };
oldName ← senderV.name;
senderV.name ← "Sending";
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; 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 { Restore; InternalReport["... Message NOT sent."] };
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: BOOLTRUE;
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: UserExec.GetNameAndPassword[].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;
} ;
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
CanonicalizeName: PUBLIC PROC[r: ROPE] RETURNS[name: ROPE] =
adds registry if r does not include one; canonicalizes " at " to "@", etc
BEGIN OPEN GVMailParse;
mPos: INT← 0;
lastCharPos: INT← r.Length[] - 1;
GetNextChar: PROC RETURNS [ch: CHAR] =
{ IF mPos > lastCharPos THEN ch← endOfInput ELSE ch← Rope.Fetch[r, mPos];
mPos← mPos + 1;  -- always increment mPos
};
BackupChar: PROC = {mPos← mPos - 1};
ProcessName: PROC[simpleName, registry, arpaHost: ROPE, nameInfo: NameInfo]
RETURNS [accept: BOOLEAN] =
BEGIN
IF nameInfo.type = msgDL THEN name← simpleName
ELSE name← CanonicalName[simpleName, registry, arpaHost, nameInfo, FALSE].name;
RETURN[FALSE];
END;
pH: ParseHandle← InitializeParse[next: GetNextChar, backup: BackupChar];
ParseNameList[pH, ProcessName ! ParseError => {name← r; CONTINUE}];
FinalizeParse[pH]
END;
CanonicalName: PUBLIC PROC
[simpleName, registry, arpaHost: ROPE, n: GVMailParse.NameInfo, sending: BOOL]
RETURNS[name: ROPE, arpaSeen: BOOL] =
BEGIN
arpaSeen← FALSE;
IF arpaHost.Length[] = 0 OR ((arpaHost.Length[] # 0) AND LocalArpaSite[arpaHost]) THEN
{ IF registry.Length[] = 0 THEN registry← defaultRegistry;
arpaHost← NIL};
name← simpleName;
IF registry.Length[] > 0 THEN name← name.Cat[".", registry];
IF arpaHost.Length[] > 0 THEN
{name← name.Cat["@", arpaHost];
IF sending THEN name← name.Concat[".ArpaGateway"];
arpaSeen← TRUE};
END;
LocalArpaSite: PUBLIC PROC[host: ROPE] RETURNS [BOOL] =
BEGIN
FOR l: LIST OF ROPE← arpaGatewayHosts, l.rest UNTIL l=NIL DO
IF Rope.Equal[host, l.first, FALSE] THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE]
END; -- of LocalArpaSite --
ParseTextToBeSent:
PROC[msg: SendingRec] RETURNS[status: SendParseStatus, sPos, mPos: INT] =
BEGIN OPEN GVMailParse;
mLF: MessageInfo;
tHeaders: LIST OF ROPENIL;
msgText: ROPE← msg.fullText;
lastCharPos: INT← msgText.Length[] - 1;
arpaSeen: BOOLFALSE;
countOfRecipients, dlCount: INT← 0;
GetNextMsgChar: PROC[] RETURNS [ch: CHAR] = {
IF mPos > lastCharPos THEN ch← endOfInput ELSE ch← Rope.Fetch[msgText, mPos];
mPos← mPos + 1 };
BackupMsgChar: PROC[] = {mPos← mPos - 1};
RNameListField: PROC[index: MessageFieldIndex] =
BEGIN
fieldBody: LIST OF RName← NIL;
AnotherRName: PROC[r1, r2, r3: ROPE, n: NameInfo] RETURNS [BOOLEAN] =
BEGIN
name: ROPE; sawArpa: BOOL;
[name, sawArpa]← CanonicalName[r1, r2, r3, n, TRUE];
arpaSeen← arpaSeen OR sawArpa;
fieldBody← CONS[name, fieldBody];
SELECT n.type FROM
normal => countOfRecipients← countOfRecipients + 1;
quotedString, file => status← includesPrivateDL;
publicDL =>
{ IF status # includesPrivateDL THEN status← includesPublicDL;
dlCount← dlCount + 1
};
ENDCASE;
RETURN[FALSE];
END;
ParseNameList[pH, AnotherRName, NIL, TRUE];
TRUSTED {fieldBody← LOOPHOLE[List.Reverse[LOOPHOLE[fieldBody]]]};
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: ROPENIL;
fieldNotRecognized: BOOL;
mPos← 0;  -- where we are in the fulltext, for parsing
status← ok;  -- start with good status
pH← InitializeParse[next: GetNextMsgChar, backup: BackupMsgChar];
make sure msgText ends in a CR
IF msgText.Fetch[lastCharPos] # '\n THEN {
msg.fullText← msgText← Rope.Concat[msgText, "\n"]; lastCharPos← lastCharPos+1};
DO
sPos← mPos;
[fieldNotRecognized, field]← GetFieldName[pH ! 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] ELSE
{IF i = replyToF THEN msg.replyTo← TRUE; []← GetFieldBody[pH]};
rCatList => []← GetFieldBody[pH];
rNameList => RNameListField[i ! ParseError => GOTO errorExit];
ENDCASE => ERROR;
EXIT
};
};
ENDLOOP;
IF fieldNotRecognized THEN []← GetFieldBody[pH]; -- skip anything not recognized
ENDLOOP;
now we are positioned at the beginning of the body of the message
FinalizeParse[pH];
msg.endHeadersPos← mPos - 1;
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
IF arpaSeen THEN msg.arpaRecipient← TRUE;
msg.numRecipients← countOfRecipients;
msg.numDLs← dlCount;
EXITS
errorExit => RETURN[syntaxError, sPos, mPos];
END;
TRUSTED { TiogaCTRL ← LOOPHOLE[1013B] };
UserProfile.CallWhenProfileChanges[PeanutSendInit];
END.