WalnutSendMailImpl.mesa - Implementation of the WalnutMsg Send operation
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Willie-Sue, December 6, 1984 2:08:01 pm PST
Donahue, October 6, 1983 1:05 pm
Russ Atkinson (RRA) March 21, 1985 0:40:15 am PST
DIRECTORY
BasicTime USING [GMT, nullGMT, Unpacked, Now, Unpack],
GVBasics USING [ItemType, RName],
GVMailParse,
GVNames,
GVSend USING [Abort, AddRecipient, AddToItem, CheckValidity, Create, Handle, Send, SendFailed, StartItem, StartSend, StartSendInfo],
Icons,
List USING [Append],
Menus,
IO,
Rope,
RuntimeError USING [BoundsFault],
WalnutDocumentRope USING[ Create ],
TiogaOps,
ViewerClasses USING [Viewer],
ViewerOps,
ViewerTools,
UserCredentials USING [Get],
WalnutSendOps,
WalnutSendInternal,
WalnutParseMsg USING [MessageFieldIndex, MessageInfo, messageParseArray, CanonicalName];
WalnutSendMailImpl:
CEDAR
MONITOR
IMPORTS BasicTime, GVMailParse, GVNames, GVSend, List, IO, Rope, RuntimeError, WalnutDocumentRope, TiogaOps, ViewerOps, ViewerTools, UserCredentials, WalnutParseMsg, WalnutSendInternal, WalnutSendOps
EXPORTS WalnutSendInternal, WalnutSendOps =
BEGIN OPEN WalnutSendInternal;
ROPE: TYPE = Rope.ROPE;
RName: TYPE = GVBasics.RName;
TiogaContents: TYPE = ViewerTools.TiogaContents;
Viewer: TYPE = ViewerClasses.Viewer;
************************************************************************
Send:
PUBLIC
PROC[v: Viewer, doClose:
BOOL←
FALSE]
RETURNS[sent:
BOOL] = {
oldMenu: Menus.Menu = v.menu;
v.inhibitDestroy← TRUE;
BEGIN
ENABLE
UNWIND =>
GOTO out;
ViewerOps.SetMenu[v, sendingMenu];
IF needToAuthenticate
THEN {
SenderReport["Authenticating user ..."];
IF ~AuthenticateUser[]
THEN {
ViewerOps.BlinkIcon[v, IF v.iconic THEN 0 ELSE 1];
ViewerOps.SetMenu[v, oldMenu];
v.inhibitDestroy← FALSE;
RETURN[FALSE]
};
SenderReport[" ...ok\n"];
};
sent← InternalSendMsg[v, doClose];
END;
ViewerOps.SetMenu[v, oldMenu];
v.inhibitDestroy← FALSE;
};
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
AppendHeaderLine:
PUBLIC
PROC[v: Viewer, line:
ROPE, changeSelection:
BOOL ←
FALSE] =
BEGIN
ENABLE RuntimeError.BoundsFault =>
GOTO exit;
text: ROPE;
i: INT← 0;
ch: CHAR;
TRUSTED {text← WalnutDocumentRope.Create[LOOPHOLE [TiogaOps.ViewerDoc[v]]]};
DO
-- find the double CR at the end of the headers
UNTIL (ch← text.Fetch[i]) = '\n DO i← i + 1; ENDLOOP;
IF (ch← text.Fetch[i← i + 1]) = '\n THEN EXIT;
ENDLOOP;
InsertIntoViewer[v, line, i, WalnutSendOps.labelFont, changeSelection];
ViewerTools.EnableUserEdits[v];
EXITS
exit => SenderReport[IO.PutFR["Malformed headers; append of &g not done", IO.rope[line]]];
END;
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
AuthenticateUser:
PROC
RETURNS [
BOOL] =
BEGIN
OPEN WalnutSendOps;
auth: GVNames.AuthenticateInfo;
IF userRName.Length[] = 0 THEN {SenderReport["Please Login\n"]; RETURN[FALSE]};
SELECT
auth←
GVNames.Authenticate[userRName, UserCredentials.Get[].password] FROM
group => SenderReport["... Can't login as group\n"];
individual => {needToAuthenticate← FALSE; RETURN[TRUE]};
notFound => {SenderReport[userRName]; SenderReport[" is invalid - please Login\n"]};
allDown => SenderReport["... No server responded\n"];
badPwd => SenderReport["... Your Password is invalid - please Login\n"];
ENDCASE;
RETURN[FALSE];
END;
InternalSendMsg:
PROC[senderV: Viewer, doClose:
BOOL]
RETURNS[sendOk:
BOOL] =
BEGIN
status: SendParseStatus;
sPos, mPos: INT;
formatting: ROPE;
smr: SendingRec;
addedTxtLength: INT;
newTxt: ROPE;
contents, currentText: ViewerTools.TiogaContents;
senderInfo: SenderInfo← NARROW[ViewerOps.FetchProp[senderV, $SenderInfo]];
sendOk← FALSE;
senderInfo.aborted← FALSE;
IF senderInfo.successfullySent
AND ~senderV.newVersion
THEN
{ SenderReport["\nDo you really want to send this message again?"];
IF ~Confirmation[senderInfo] THEN
{ SenderReport[" .. Not sent\n"];
senderInfo.successfullySent← FALSE;
RETURN}
};
senderInfo.successfullySent← FALSE;
SenderReport["... Parsing..."];
smr← NEW[SendMsgRecObject];
TRUSTED
{smr.fullText←
WalnutDocumentRope.Create[LOOPHOLE [TiogaOps.ViewerDoc[senderV]]]};
IF smr.fullText.Length[] >
LAST[
CARDINAL]
THEN
{ SenderReport["\nMessage is too long to send; NOT sent\n"]; RETURN};
[status, sPos, mPos]← ParseTextToBeSent[smr];
IF (status # ok)
AND (status # includesPublicDL)
THEN
BEGIN
SELECT status
FROM
fieldNotAllowed =>
IF sPos # mPos
THEN
{ ShowErrorFeedback[senderV, sPos, mPos];
SenderReport[Rope.Substr[smr.fullText, MAX[0, sPos-1], mPos-sPos]];
SenderReport[" field is not allowed\n"]}
ELSE SenderReport[IO.PutFR[" field at pos %g is not allowed\n", IO.int[sPos]]];
syntaxError =>
IF sPos # mPos
THEN
{ ShowErrorFeedback[senderV, sPos, mPos];
SenderReport["\nSyntax error on line beginning with "];
SenderReport[Rope.Substr[smr.fullText, MAX[0, sPos-1], mPos-sPos]]}
ELSE SenderReport[IO.PutFR["..... Syntax error at position %g ", IO.int[sPos]]];
includesPrivateDL => SenderReport[" Private dl's are not yet implemented\n"];
ENDCASE => ERROR;
ViewerOps.BlinkIcon[senderV, IF senderV.iconic THEN 0 ELSE 1];
RETURN;
END;
IF CheckForAbortSend[senderInfo] THEN RETURN;
IF (status = includesPublicDL
OR smr.numRecipients > maxWithNoReplyTo)
AND
~smr.replyTo THEN
{ howToReply: HowToReplyTo← self;
IF ~replyToSelf
THEN
{ oldM: Menus.Menu← senderV.menu;
IF CheckForAbortSend[senderInfo] THEN RETURN;
ViewerOps.SetMenu[senderV, replyToMenu];
SenderReport[
IO.PutFR["... %g public DLs and %g other recipients; please choose Reply-To option",
IO.int[smr.numDLs], IO.int[smr.numRecipients]]];
SenderReport[
"\nClick Self to reply-to self, All to reply-to all, Cancel to cancel Send\n"];
howToReply← ReplyToResponse[senderInfo];
ViewerOps.SetMenu[senderV, oldM];
};
IF howToReply # all
THEN
InsertIntoViewer[senderV, Rope.Concat["Reply-to: ", WalnutSendOps.userRName],
smr.endHeadersPos, WalnutSendOps.labelFont];
IF howToReply = cancel THEN
{SenderReport["\nDelivery cancelled. Reply-to: has been added\n"];
RETURN
};
IF CheckForAbortSend[senderInfo] THEN RETURN;
};
IF doClose AND ~senderV.iconic THEN ViewerOps.CloseViewer[senderV];
first add the From:/Sender: line
currentText← ViewerTools.GetTiogaContents[senderV];
newTxt ←
Rope.Cat[IF smr.from = NIL THEN "From: " ELSE "Sender: ", WalnutSendOps.userRName];
addedTxtLength ← newTxt.Length[];
InsertIntoViewer[senderV, newTxt, 0, WalnutSendOps.labelFont];
now put the date at the very front
newTxt ← Rope.Concat["Date: ", RFC822Date[]];
addedTxtLength ← newTxt.Length[] + addedTxtLength + 1;
InsertIntoViewer[senderV, newTxt, 0, WalnutSendOps.labelFont];
contents← ViewerTools.GetTiogaContents[senderV];
note that there is guaranteed to be formatting since we added some to make things look nice
{ 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];
formatting ← contents.formatting } };
IF smr.subject.Length[] > 40
THEN
smr.subject← Rope.Concat[Rope.Substr[smr.subject, 0, 35], " ..."];
smr.subject← Rope.Cat[" \"", smr.subject, "\" "];
SenderReport["... Sending "]; SenderReport[smr.subject];
IF senderInfo.successfullySent← sendOk← SendIt[smr, senderInfo, formatting]
THEN
{ SenderReport[smr.subject]; SenderReport[" has been delivered\n"];
senderInfo.prevMsg← currentText;
}
ELSE
{ DeleteChars[senderV, addedTxtLength];
SenderReport[smr.subject]; SenderReport[" NOT sent\n"];
};
END;
RFC822Date: PUBLIC PROC[gmt: BasicTime.GMT← BasicTime.nullGMT] RETURNS[date: ROPE] =
generates arpa standard time, dd mmm yy hh:mm:ss zzz
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";
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;
ShowErrorFeedback:
PUBLIC
PROC[v: Viewer, start, end:
INT] =
BEGIN OPEN TiogaOps;
ENABLE
UNWIND =>
GOTO exit;
startLoc, endLoc: Location;
thisV: Ref = ViewerDoc[v];
beginning: Location← [FirstChild[thisV], 0];
startLoc← LocRelative[beginning, start];
endLoc← LocRelative[beginning, end];
SetSelection[viewer: v, start: startLoc, end: endLoc, which: feedback];
END;
InsertIntoViewer:
PUBLIC
PROC
[v: Viewer, what: ROPE, where: INT, labelFont: ROPE ← NIL, changeSelection: BOOL ← FALSE] =
BEGIN
OPEN TiogaOps;
thisV: Ref = ViewerDoc[v];
prefix: ROPE;
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];
IF labelFont # NIL THEN TiogaOps.SetLooks[labelFont, caret];
TiogaOps.InsertRope[prefix];
IF labelFont # NIL THEN TiogaOps.ClearLooks[caret];
TiogaOps.InsertRope[what];
TiogaOps.Break[]; -- make a new node
ViewerTools.InhibitUserEdits[v];
IF ~changeSelection
AND (prevV # v)
AND (prevV#
NIL)
AND ~prevV.destroyed
THEN
SetSelection[prevV, prevStart, prevEnd, prevLevel, cb, pd !
TiogaOps.SelectionError => CONTINUE];
END;
LockViewerOpen[v];
prefix ← Rope.Substr[ what, 0, Rope.Find[s1: what, s2: ":"] ];
what ← Rope.Replace[base: what, start: 0, len: prefix.Length, with:
NIL];
IF thisV = NIL THEN InsertChars[thisV] ELSE CallWithLocks[InsertChars, thisV];
ReleaseOpenViewer[v];
END;
DeleteChars:
PUBLIC
ENTRY
PROC[v: Viewer, num:
INT] =
BEGIN
ENABLE
UNWIND =>
NULL;
IF num # 0 THEN DeleteLeadingChars[v, num]
END;
DeleteLeadingChars:
INTERNAL
PROC[v: Viewer, num:
INT] =
BEGIN
OPEN TiogaOps;
thisV: Ref← ViewerDoc[v];
DelChars:
PROC[root: Ref] =
BEGIN
prevV: Viewer;
prevStart, prevEnd: Location;
prevLevel: SelectionGrain;
cb, pd: BOOL;
startLoc: Location← [FirstChild[thisV], 0];
endLoc: Location← LocRelative[startLoc, num];
[prevV, prevStart, prevEnd, prevLevel, cb, pd]← GetSelection[primary];
ViewerTools.EnableUserEdits[v];
SetSelection[viewer: v, start: startLoc, end: endLoc];
Delete[];
GoToNextNode[];
Join[];
IF (prevV # v)
AND (prevV#
NIL)
THEN
SetSelection[prevV, prevStart, prevEnd, prevLevel, cb, pd];
END;
IF thisV = NIL THEN DelChars[thisV] ELSE CallWithLocks[DelChars, thisV];
END;
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
SendIt:
PROC[smr: SendingRec, senderInfo: SenderInfo, formatting:
ROPE]
RETURNS[ sent:
BOOLEAN] =
{
InitSending:
ENTRY
PROC =
{
ENABLE
UNWIND =>
NULL;
senderInfo.sendHandle← GVSend.Create[];
senderInfo.validateFlag← TRUE;
senderInfo.aborted← FALSE;
};
FinishedSending:
ENTRY
PROC =
{
ENABLE
UNWIND =>
NULL;
senderInfo.sendHandle← NIL
};
InitSending[];
sent ← SendMessage[smr, senderInfo, formatting ! GVSend.SendFailed =>
{
IF notDelivered
THEN
{ SenderReport["\nCommunication failure during send. Retry?\n"];
IF Confirmation[senderInfo] THEN RETRY
ELSE { sent ← FALSE; CONTINUE }
}
ELSE {
SenderReport["\nCommunication failure, but message delivered\n"];
sent ← TRUE;
CONTINUE;
};
}];
FinishedSending[];
} ;
SendMessage:
PROC[smr: SendingRec, senderInfo: SenderInfo, formatting:
ROPE]
RETURNS[ sent: BOOLEAN ] = {
DO ENABLE GVSend.SendFailed => CONTINUE; -- try again if SendFailed
h: GVSend.Handle ← senderInfo.sendHandle;
startInfo: GVSend.StartSendInfo;
stepper: LIST OF RName;
tempInteger: INT;
numRecips: INT ← 0;
numValidRecips: INT;
firstInvalidUser: BOOL← TRUE;
numInvalidUsers: INTEGER← 0;
InvalidUserProc:
PROC [ userNum:
INT, userName: RName ] = {
IF firstInvalidUser THEN {SenderReport["\nInvalid user(s): "]; firstInvalidUser← FALSE};
SELECT numInvalidUsers ← numInvalidUsers + 1
FROM
1 => SenderReport[userName];
IN [2..5] => {SenderReport[", "]; SenderReport[userName]};
6 => SenderReport[", ...\n"];
ENDCASE;
} ;
sent ← FALSE ;
startInfo ← GVSend.StartSend[ handle: senderInfo.sendHandle,
senderPwd: UserCredentials.Get[].password,
sender: WalnutSendOps.userRName,
returnTo: WalnutSendOps.userRName,
validate: senderInfo.validateFlag
] ;
SELECT startInfo
FROM
badPwd => {SenderReport["\nInvalid password\n"]; RETURN};
badSender => {SenderReport["\nInvalid sender name\n"]; RETURN};
badReturnTo => {SenderReport["\nBad return-to field\n"]; RETURN};
allDown => {SenderReport["\nAll servers are down\n"]; RETURN};
ok => {
stepper ← smr.to;
WHILE stepper #
NIL
DO
GVSend.AddRecipient[ h, stepper.first ];
numRecips ← numRecips + 1;
stepper ← stepper.rest;
ENDLOOP;
IF CheckForAbortSend[senderInfo] THEN RETURN;
stepper ← smr.cc;
WHILE stepper #
NIL
DO
GVSend.AddRecipient[ h, stepper.first ] ;
numRecips ← numRecips + 1 ;
stepper ← stepper.rest ;
ENDLOOP ;
IF CheckForAbortSend[senderInfo]
THEN
RETURN;
IF senderInfo.validateFlag THEN
{
IF (numValidRecips← GVSend.CheckValidity[ h, InvalidUserProc]) = 0
THEN
{ SenderReport["\nThere were NO valid recipients; do you wish to send anyway?\n"];
IF CheckForAbortSend[senderInfo]
OR ~Confirmation[senderInfo]
THEN
{GVSend.Abort[h]; RETURN};
};
IF numValidRecips # numRecips
THEN
{ tempInteger ← numRecips-numValidRecips;
SenderReport[IO.PutFR["\nThere were %g invalid recipients,", IO.int[tempInteger] ]];
SenderReport[" do you wish to send anyway?\n"];
IF CheckForAbortSend[senderInfo]
OR ~Confirmation[senderInfo]
THEN
{GVSend.Abort[h]; RETURN};
};
};
IF CheckForAbortSend[senderInfo] THEN RETURN;
SenderReport[IO.PutFR["..sending to %g recipients\n", IO.int[numValidRecips]]];
senderInfo.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, WalnutSendOps.TiogaCTRL]; GVSend.AddToItem[h, formatting] };
IF smr.voiceID.Length[] # 0
THEN
{ GVSend.StartItem[h, Audio]; GVSend.AddToItem[h, smr.voiceID] };
ViewerOps.SetMenu[senderInfo.senderV, blankMenu];
IF CheckForAbortSend[senderInfo] THEN RETURN;
GVSend.Send[ h ] ;
sent← TRUE;
RETURN;
} ;
ENDCASE ;
ENDLOOP;
} ;
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
ParseTextToBeSent:
PROC[msg: SendingRec]
RETURNS[status: SendParseStatus, sPos, mPos:
INT] =
BEGIN
OPEN GVMailParse;
mLF: WalnutParseMsg.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: WalnutParseMsg.MessageFieldIndex] =
BEGIN
fieldBody, fbEnd: LIST OF RName← NIL;
AnotherRName:
PROC[r1, r2:
ROPE, isFile, isNested:
BOOL]
RETURNS [
ROPE,
BOOLEAN] =
BEGIN
name: ROPE;
name← WalnutParseMsg.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
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: WalnutParseMsg.MessageFieldIndex IN WalnutParseMsg.MessageFieldIndex DO
{ mLF← WalnutParseMsg.messageParseArray[i];
IF Rope.Equal[WalnutParseMsg.messageParseArray[i].name, field,
FALSE]
THEN
{ 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;
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
msg.numRecipients← countOfRecipients;
msg.numDLs← dlCount;
EXITS
errorExit => RETURN[syntaxError, sPos, mPos];
END;
END.